## Getting Started
+
To use `VMR.ahk` in your script, follow one of the following methods:
-### A - ahkpm installation
-1. Install and set up [ahkpm](https://github.com/joshuacc/ahkpm), then run `ahkpm install gh:SaifAqqad/VMR.ahk`
-2. Run `ahkpm include -f my-script.ahk gh:SaifAqqad/VMR.ahk` to add an include directive in your script
- ###### Replace *my-script.ahk* with your script's path
+### A. ahkpm installation
+
+1. Install and set up [ahkpm](https://github.com/joshuacc/ahkpm)
+2. Run `ahkpm install gh:SaifAqqad/VMR.ahk`
+3. Include VMR in your script by running `ahkpm include gh:SaifAqqad/VMR.ahk -f myScript.ahk`
+ ###### Replace *myScript.ahk* with your script's path
+
+### B. Manual Installation
-### B - Manual Installation
-1. Download the latest pre-built version from the [`dist` folder](https://raw.githubusercontent.com/SaifAqqad/VMR.ahk/master/dist/VMR.ahk) or follow the build instructions below
+1. Download the latest pre-built version from the [`dist` folder](https://raw.githubusercontent.com/SaifAqqad/VMR.ahk/master/dist/VMR.ahk) / [latest release](https://github.com/SaifAqqad/VMR.ahk/releases) or follow the build instructions below
+2. Include it using `#Include VMR.ahk` or copy it to a [library folder](https://www.autohotkey.com/docs/v2/Scripts.htm#lib) and use `#Include `
-2. Include it using `#Include VMR.ahk` or copy it to a [library folder](https://www.autohotkey.com/docs/Functions.htm#lib) and use `#Include `
+> [!IMPORTANT]
+> The current version of VMR ***only*** supports AHK v2, The AHK v1 version is still available on the [v1 branch](https://github.com/SaifAqqad/VMR.ahk/tree/v1) but will (probably) not receive any updates.
-3. Create an instance of the VMR class and log in to the API:
- ```ahk
- voicemeeter:= new VMR().login()
- ```
-4. The `VMR` object will have two arrays (`bus` and`strip`), as well as other objects, that will allow you to control voicemeeter in AHK.
- ```ahk
- voicemeeter.bus[1].mute:= true
- voicemeeter.strip[4].gain++
- ```
+## Basic usage
+- Create an instance of the `VMR` class and log in to the API:
+ ```ahk
+ voicemeeter := VMR().Login()
+ ```
+- The `VMR` instance will have two arrays (`Bus` and `Strip`), as well as other properties/methods that will allow you to control voicemeeter in AHK
+ ```ahk
+ voicemeeter.Bus[1].mute := true
+ voicemeeter.Strip[4].gain++
+ ```
+ ##### For more info, check out the [documentation](https://saifaqqad.github.io/VMR.ahk/) and the [examples](./examples/)
## Build instructions
-To build `VMR.ahk` yourself, run the build script:
+
+To build `VMR.ahk`, either run the vscode task `Build VMR` or run the build script using ahkpm or manually:
+
```powershell
-.\Build.ahk
+# ahkpm
+ahkpm run build
+# Manually
+Autohotkey.exe ".\Build.ahk" ".\VMR.ahk" "..\dist\VMR.ahk" ""
```
-##### For more info, check out the [documentation](https://saifaqqad.github.io/VMR.ahk/)
diff --git a/ahkpm.json b/ahkpm.json
index 456d7fe..db8fd7b 100644
--- a/ahkpm.json
+++ b/ahkpm.json
@@ -1,5 +1,5 @@
{
- "version": "1.2.0",
+ "version": "2.0.0",
"description": "AutoHotkey wrapper class for Voicemeeter's Remote API",
"repository": "https://github.com/SaifAqqad/VMR.ahk",
"website": "https://saifaqqad.github.io/VMR.ahk",
@@ -11,5 +11,8 @@
"email": "",
"website": ""
},
+ "scripts": {
+ "build": "& .\\src\\Build.ahk '.\\src\\VMR.ahk' '.\\dist\\VMR.ahk' 'ahkpm'"
+ },
"dependencies": {}
-}
+}
\ No newline at end of file
diff --git a/dist/VMR.ahk b/dist/VMR.ahk
index 9b505d6..1f08bec 100644
--- a/dist/VMR.ahk
+++ b/dist/VMR.ahk
@@ -1,851 +1,2052 @@
-;
-; VMR.ahk v1.2.0
-; Build timestamp: 20230520071651
-; Repo: https://github.com/SaifAqqad/VMR.ahk
-; Docs: https://saifaqqad.github.io/VMR.ahk
-;
-class VMR{
- bus:=""
- , strip:=""
- , recorder:=""
- , option:=""
- , patch:=""
- , fx:=""
- , onUpdateLevels:=""
- , onUpdateParameters:=""
- , onUpdateMacrobuttons:=""
- , onMidiMessage:=""
-
- __New(p_path:=""){
- VBVMR.DLL_PATH := p_path? p_path : this.__getDLLPath()
- VBVMR.DLL_FILE := A_PtrSize = 8 ? "VoicemeeterRemote64.dll" : "VoicemeeterRemote.dll"
- if(!FileExist(VBVMR.DLL_PATH . "\" . VBVMR.DLL_FILE))
- Throw, Exception(Format("Voicemeeter is not installed in the path :`n{}", VBVMR.DLL_PATH))
- VBVMR.STR_TYPE := A_IsUnicode? "W" : "A"
- VBVMR.DLL := DllCall("LoadLibrary", "Str", VBVMR.DLL_PATH . "\" . VBVMR.DLL_FILE, "Ptr")
- VBVMR.__getAddresses()
- }
-
- login(){
- if(VBVMR.Login()){
- this.runVoicemeeter()
- WinWait, ahk_class VBCABLE0Voicemeeter0MainWindow0
- sleep, 2000
- }
- OnExit(ObjBindMethod(this, "__onExit"))
- syncWithDLL := ObjBindMethod(this, "__syncWithDLL")
- SetTimer, %syncWithDLL%, 20
- this.getType()
- this.__init_arrays()
- this.__init_obj()
- this.__syncWithDLL()
- return this
+/**
+ * VMR.ahk - A wrapper for Voicemeeter's Remote API
+ * - Version 2.0.0
+ * - Build timestamp 2024-03-09 19:51:57 UTC
+ * - Repository: {@link https://github.com/SaifAqqad/VMR.ahk GitHub}
+ * - Documentation: {@link https://saifaqqad.github.io/VMR.ahk VMR Docs}
+ */
+#Requires AutoHotkey >=2.0
+class VMRUtils {
+ static _MIN_PERCENTAGE := 0.001
+ static _MAX_PERCENTAGE := 1.0
+ /**
+ * Converts a dB value to a percentage value.
+ *
+ * @param {Number} p_dB The dB value to convert.
+ * __________
+ * @returns {Number} The percentage value.
+ */
+ static DbToPercentage(p_dB) {
+ local value := ((10 ** (p_dB / 20)) - VMRUtils._MIN_PERCENTAGE) / (VMRUtils._MAX_PERCENTAGE - VMRUtils._MIN_PERCENTAGE)
+ return value < 0 ? 0 : Round(value * 100)
}
-
-
- getType(){
- if(!VBVMR.VM_TYPE){
- VBVMR.VM_TYPE:= VBVMR.GetVoicemeeterType()
- Switch VBVMR.VM_TYPE {
- case 1:
- VBVMR.BUSCOUNT:= 2
- VBVMR.STRIPCOUNT:= 3
- VBVMR.VBANINCOUNT:= 4
- VBVMR.VBANOUTCOUNT:= 4
- case 2:
- VBVMR.BUSCOUNT:= 5
- VBVMR.STRIPCOUNT:= 5
- VBVMR.VBANINCOUNT:= 8
- VBVMR.VBANOUTCOUNT:= 8
+ /**
+ * Converts a percentage value to a dB value.
+ *
+ * @param {Number} p_percentage The percentage value to convert.
+ * __________
+ * @returns {Number} The dB value.
+ */
+ static PercentageToDb(p_percentage) {
+ if (p_percentage < 0)
+ p_percentage := 0
+ local value := 20 * Log(VMRUtils._MIN_PERCENTAGE + p_percentage / 100 * (VMRUtils._MAX_PERCENTAGE - VMRUtils._MIN_PERCENTAGE))
+ return Round(value, 2)
+ }
+ /**
+ * Applies an upper and a lower bound on a passed value.
+ *
+ * @param {Number} p_value The value to apply the bounds on.
+ * @param {Number} p_min The lower bound.
+ * @param {Number} p_max The upper bound.
+ * __________
+ * @returns {Number} The value with the bounds applied.
+ */
+ static EnsureBetween(p_value, p_min, p_max) => Round(Max(p_min, Min(p_max, p_value)), 2)
+ /**
+ * Returns the index of the first occurrence of a value in an array, or -1 if it's not found.
+ *
+ * @param {Array} p_array The array to search in.
+ * @param {Any} p_value The value to search for.
+ * __________
+ * @returns {Number} The index of the first occurrence of the value in the array, or -1 if it's not found.
+ */
+ static IndexOf(p_array, p_value) {
+ local i, value
+ if !(p_array is Array)
+ throw Error("p_array: Expected an Array, got " Type(p_array))
+ for (i, value in p_array) {
+ if (value = p_value)
+ return i
+ }
+ return -1
+ }
+ /**
+ * Returns a string with the passed parameters joined using the passed seperator.
+ *
+ * @param {Array} p_params - The parameters to join.
+ * @param {String} p_seperator - The seperator to use.
+ * @param {Number} p_maxLength - The maximum length of each parameter.
+ * __________
+ * @returns {String} The joined string.
+ */
+ static Join(p_params, p_seperator, p_maxLength := 30) {
+ local str := ""
+ for (param in p_params) {
+ str .= SubStr(VMRUtils.ToString(param), 1, p_maxLength) . p_seperator
+ }
+ return SubStr(str, 1, -StrLen(p_seperator))
+ }
+ /**
+ * Converts a value to a string.
+ *
+ * @param {Any} p_value The value to convert to a string.
+ * _________
+ * @returns {String} The string representation of the passed value
+ */
+ static ToString(p_value) {
+ if (p_value is String)
+ return p_value
+ else if (p_value is Array)
+ return "[" . VMRUtils.Join(p_value, ", ") . "]"
+ else if (IsObject(p_value))
+ return p_value.ToString ? p_value.ToString() : Type(p_value)
+ else
+ return String(p_value)
+ }
+}
+class VMRError extends Error {
+ /**.
+ * The return code of the Voicemeeter function that failed
+ * @type {Number}
+ */
+ ReturnCode := ""
+ /**
+ * The name of the function that threw the error
+ * @type {String}
+ */
+ What := ""
+ /**
+ * An error message
+ * @type {String}
+ */
+ Message := ""
+ /**
+ * Extra information about the error
+ * @type {String}
+ */
+ Extra := ""
+ /**
+ * @param {Any} p_errorValue - The error value
+ * @param {String} p_funcName - The name of the function that threw the error
+ * @param {Array} p_funcParams The parameters of the function that threw the error
+ */
+ __New(p_errorValue, p_funcName, p_funcParams*) {
+ this.What := p_funcName
+ this.Extra := p_errorValue
+ this.Message := "VMR failure in " p_funcName "(" VMRUtils.Join(p_funcParams, ", ") ")"
+ if (p_errorValue is Error) {
+ this.Extra := "Inner error message (" p_errorValue.Message ")"
+ }
+ else if (IsNumber(p_errorValue)) {
+ this.ReturnCode := p_errorValue
+ this.Extra := "VMR Return Code (" p_errorValue ")"
+ }
+ }
+}
+class VMRConsts {
+ /**
+ * Events fired by the {@link VMR|`VMR`} object.
+ * Use {@link @VMR.On|`VMR.On`} to register event listeners.
+ *
+ * @event `ParametersChanged` - Called when bus/strip parameters change
+ * @event `LevelsUpdated` - Called when the {@link @VMRAudioIO.Level|`Level`} arrays for bus/strips are updated
+ * @event `DevicesUpdated` - Called when the list of available devices is updated
+ * @event `MacroButtonsChanged` - Called when macro-buttons's states change
+ * @event `MidiMessage` - Called when a midi message is received
+ * - The `MidiMessage` callback will be passed an array with the hex-formatted bytes of the message
+ */
+ static Events := {
+ ParametersChanged: "ParametersChanged",
+ LevelsUpdated: "LevelsUpdated",
+ DevicesUpdated: "DevicesUpdated",
+ MacroButtonsChanged: "MacroButtonsChanged",
+ MidiMessage: "MidiMessage"
+ }
+ /**
+ * Default names for Voicemeeter buses
+ * @type {Array}
+ */
+ static BUS_NAMES := [
+ ; Voicemeeter
+ ["A", "B"],
+ ; Voicemeeter Banana
+ ["A1", "A2", "A3", "B1", "B2"],
+ ; Voicemeeter Potato
+ ["A1", "A2", "A3", "A4", "A5", "B1", "B2", "B3"]
+ ]
+ static STRIP_NAMES := [
+ ; Voicemeeter
+ ["Input #1", "Input #2", "Virtual Input #1"],
+ ; Voicemeeter Banana
+ ["Input #1", "Input #2", "Input #3", "Virtual Input #1", "Virtual Input #2"],
+ ; Voicemeeter Potato
+ ["Input #1", "Input #2", "Input #3", "Input #4", "Input #5", "Virtual Input #1", "Virtual Input #2", "Virtual Input #3"]
+ ]
+ /**
+ * Known string parameters for {@link VMRAudioIO|`VMRAudioIO`}
+ * @type {Array}
+ */
+ static IO_STRING_PARAMETERS := [
+ "Device",
+ "Device.name",
+ "Device.wdm",
+ "Device.mme",
+ "Device.ks",
+ "Device.asio",
+ "Label",
+ "FadeTo",
+ "FadeBy",
+ "AppGain",
+ "AppMute"
+ ]
+ /**
+ * Known device drivers
+ * @type {Array}
+ */
+ static DEVICE_DRIVERS := ["wdm", "mme", "asio", "ks"]
+ /**
+ * Default device driver, used when setting a device without specifying a driver
+ * @type {String}
+ */
+ static DEFAULT_DEVICE_DRIVER := "wdm"
+ static REGISTRY_KEY := Format("HKLM\Software{}\Microsoft\Windows\CurrentVersion\Uninstall\VB:Voicemeeter {17359A74-1236-5467}", A_Is64bitOS ? "\WOW6432Node" : "")
+ static DLL_FILE := A_PtrSize == 8 ? "VoicemeeterRemote64.dll" : "VoicemeeterRemote.dll"
+ static WM_DEVICE_CHANGE := 0x0219, WM_DEVICE_CHANGE_PARAM := 0x0007
+ static SYNC_TIMER_INTERVAL := 10, LEVELS_TIMER_INTERVAL := 30
+ static AUDIO_IO_GAIN_MIN := -60.0, AUDIO_IO_GAIN_MAX := 12.0
+ static AUDIO_IO_LIMIT_MIN := -40.0, AUDIO_IO_LIMIT_MAX := 12.0
+}
+class VMRDevice {
+ __New(name, driver, hwid) {
+ this.Name := name
+ this.Hwid := hwid
+ if (IsNumber(driver)) {
+ switch driver {
case 3:
- VBVMR.BUSCOUNT:= 8
- VBVMR.STRIPCOUNT:= 8
- VBVMR.VBANINCOUNT:= 8
- VBVMR.VBANOUTCOUNT:= 8
+ driver := "wdm"
+ case 4:
+ driver := "ks"
+ case 5:
+ driver := "asio"
+ default:
+ driver := "mme"
}
}
- return VBVMR.VM_TYPE
- }
-
- runVoicemeeter(p_type := ""){
- if(p_type){
- Run, % VBVMR.DLL_PATH "\" this.__getTypeExecutable(p_type) , % VBVMR.DLL_PATH, UseErrorLevel Hide
- }else{
- loop 3 {
- Run, % VBVMR.DLL_PATH "\" this.__getTypeExecutable(4-A_Index) , % VBVMR.DLL_PATH, UseErrorLevel Hide
- if(!ErrorLevel)
- return
- }
+ this.Driver := driver
+ }
+ ToString() {
+ return this.name
+ }
+}
+/**
+ * A static wrapper class for the Voicemeeter Remote DLL.
+ *
+ * Must be initialized by calling {@link VBVMR.Init|`Init()`} before using any of its static methods.
+ */
+class VBVMR {
+ static FUNC := {
+ Login: 0,
+ Logout: 0,
+ SetParameterFloat: 0,
+ SetParameterStringW: 0,
+ GetParameterFloat: 0,
+ GetParameterStringW: 0,
+ GetVoicemeeterType: 0,
+ GetVoicemeeterVersion: 0,
+ GetLevel: 0,
+ Output_GetDeviceNumber: 0,
+ Output_GetDeviceDescW: 0,
+ Input_GetDeviceNumber: 0,
+ Input_GetDeviceDescW: 0,
+ IsParametersDirty: 0,
+ MacroButton_IsDirty: 0,
+ MacroButton_GetStatus: 0,
+ MacroButton_SetStatus: 0,
+ GetMidiMessage: 0,
+ SetParameters: 0,
+ SetParametersW: 0
+ }
+ static DLL := "", DLL_PATH := ""
+ /**
+ * Initializes the VBVMR class by loading the Voicemeeter Remote DLL and getting the addresses of all needed functions.
+ * If the DLL is already loaded, it returns immediately.
+ * @param {String} p_path - (Optional) The path to the Voicemeeter Remote DLL. If not specified, it will be looked up in the registry.
+ * __________
+ * @throws {VMRError} - If the DLL is not found in the specified path or if voicemeeter is not installed.
+ */
+ static Init(p_path := "") {
+ if (VBVMR.DLL != "")
+ return
+ VBVMR.DLL_PATH := p_path ? p_path : VBVMR._GetDLLPath()
+ local dllPath := VBVMR.DLL_PATH "\" VMRConsts.DLL_FILE
+ if (!FileExist(dllPath))
+ throw VMRError("Voicemeeter is not installed in the path :`n" . dllPath, VBVMR.Init.Name, p_path)
+ ; Load the voicemeeter DLL
+ VBVMR.DLL := DllCall("LoadLibrary", "Str", dllPath, "Ptr")
+ ; Get the addresses of all needed function
+ for (fName in VBVMR.FUNC.OwnProps()) {
+ VBVMR.FUNC.%fName% := DllCall("GetProcAddress", "Ptr", VBVMR.DLL, "AStr", "VBVMR_" . fName, "Ptr")
}
- if(ErrorLevel)
- Throw, Exception("Could not run Voicemeeter")
- }
-
- updateDevices(){
- VMR.BusStrip.BusDevices:= Array()
- VMR.BusStrip.StripDevices:= Array()
- loop % VBVMR.Output_GetDeviceNumber()
- VMR.BusStrip.BusDevices.Push(VBVMR.Output_GetDeviceDesc(A_Index-1))
- loop % VBVMR.Input_GetDeviceNumber()
- VMR.BusStrip.StripDevices.Push(VBVMR.Input_GetDeviceDesc(A_Index-1))
- }
-
- getBusDevices(){
- return VMR.BusStrip.BusDevices
- }
-
- getStripDevices(){
- return VMR.BusStrip.StripDevices
- }
-
- exec(script){
- Try errLn:= VBVMR.SetParameters(script)
- if(errLn != 0)
- Throw, Exception("exec:`nScript error at line " . errLn)
- return errLn
- }
-
- __getDLLPath(){
- vmkey := "VB:Voicemeeter {17359A74-1236-5467}"
- key := "HKLM\Software{}\Microsoft\Windows\CurrentVersion\Uninstall\{}"
- Try RegRead, value, % Format(key, A_Is64bitOS?"\WOW6432Node":"", vmkey), UninstallString
- catch {
- Throw, Exception("Voicemeeter is not installed")
- }
- SplitPath, value,, dir
+ }
+ /**
+ * @private - Internal method
+ * @description Looks up the installation path of Voicemeeter in the registry.
+ * __________
+ * @returns {String} - The installation path of Voicemeeter.
+ */
+ static _GetDLLPath() {
+ local value := "", dir := ""
+ try
+ value := RegRead(VMRConsts.REGISTRY_KEY, "UninstallString")
+ catch OSError
+ throw VMRError("Failed to retrieve the installation path of Voicemeeter", VBVMR._GetDLLPath.Name)
+ SplitPath(value, , &dir)
return dir
}
-
- __init_obj(){
- this.option:= new this.OptionBase
- this.vban.init()
- this.vban.stream.initiated:=1
- if(this.getType() >= 2){
- this.patch:= new this.PatchBase
- this.recorder:= new this.RecorderBase
- }
- if(this.getType() >= 3)
- this.fx := new this.FXBase
- }
-
- __init_arrays(){
- this.bus:= Array()
- this.strip:= Array()
- loop % VBVMR.BUSCOUNT {
- this.bus.Push(new this.BusStrip("Bus"))
- }
- loop % VBVMR.STRIPCOUNT {
- this.strip.Push(new this.BusStrip("Strip"))
- }
- this.updateDevices()
- VMR.BusStrip.initiated:=1
- }
-
- __getTypeExecutable(p_type){
- switch (p_type) {
- case 1: return "voicemeeter.exe"
- case 2: return "voicemeeterpro.exe"
- case 3: return Format("voicemeeter8{}.exe", A_Is64bitOS? "x64":"")
- }
- }
-
- __syncWithDLL(){
- static ignore_msg:=0
+ /**
+ * Opens a Communication Pipe With Voicemeeter.
+ * __________
+ * @returns {Number}
+ * - `0` : OK (no error).
+ * - `1` : OK but Voicemeeter is not launched (need to launch it manually).
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Login() {
+ local result
+ try result := DllCall(VBVMR.FUNC.Login)
+ catch Error as err
+ throw VMRError(err, VBVMR.Login.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Login.Name)
+ return result
+ }
+ /**
+ * Closes the Communication Pipe With Voicemeeter.
+ * __________
+ * @returns {Number}
+ * - `0` : OK (no error).
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Logout() {
+ local result
+ try result := DllCall(VBVMR.FUNC.Logout)
+ catch Error as err
+ throw VMRError(err, VBVMR.Logout.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Logout.Name)
+ return result
+ }
+ /**
+ * Sets the value of a float (numeric) parameter.
+ * @param {String} p_prefix - The prefix of the parameter, usually the name of the bus/strip (ex: `Bus[0]`).
+ * @param {String} p_parameter - The name of the parameter (ex: `gain`).
+ * @param {Number} p_value - The value to set.
+ * __________
+ * @returns {Number}
+ * - `0` : OK (no error).
+ * @throws {VMRError} - If the parameter is not found, or an internal error occurs.
+ */
+ static SetParameterFloat(p_prefix, p_parameter, p_value) {
+ local result
+ try result := DllCall(VBVMR.FUNC.SetParameterFloat, "AStr", p_prefix . "." . p_parameter, "Float", p_value, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.SetParameterFloat.Name, p_prefix, p_parameter, p_value)
+ if (result < 0)
+ throw VMRError(result, VBVMR.SetParameterFloat.Name, p_prefix, p_parameter, p_value)
+ return result
+ }
+ /**
+ * Sets the value of a string parameter.
+ * @param {String} p_prefix - The prefix of the parameter, usually the name of the bus/strip (ex: `Strip[1]`).
+ * @param {String} p_parameter - The name of the parameter (ex: `name`).
+ * @param {String} p_value - The value to set.
+ * __________
+ * @returns {Number}
+ * - `0` : OK (no error).
+ * @throws {VMRError} - If the parameter is not found, or an internal error occurs.
+ */
+ static SetParameterString(p_prefix, p_parameter, p_value) {
+ local result
+ try result := DllCall(VBVMR.FUNC.SetParameterStringW, "AStr", p_prefix . "." . p_parameter, "WStr", p_value, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.SetParameterString.Name, p_prefix, p_parameter, p_value)
+ if (result < 0)
+ throw VMRError(result, VBVMR.SetParameterString.Name, p_prefix, p_parameter, p_value)
+ return result
+ }
+ /**
+ * Returns the value of a float (numeric) parameter.
+ * @param {String} p_prefix - The prefix of the parameter, usually the name of the bus/strip (ex: `Bus[2]`).
+ * @param {String} p_parameter - The name of the parameter (ex: `gain`).
+ * __________
+ * @returns {Number} - The value of the parameter.
+ * @throws {VMRError} - If the parameter is not found, or an internal error occurs.
+ */
+ static GetParameterFloat(p_prefix, p_parameter) {
+ local result, value := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.GetParameterFloat, "AStr", p_prefix . "." . p_parameter, "Ptr", value, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.GetParameterFloat.Name, p_prefix, p_parameter)
+ if (result < 0)
+ throw VMRError(result, VBVMR.GetParameterFloat.Name, p_prefix, p_parameter)
+ value := NumGet(value, 0, "Float")
+ return value
+ }
+ /**
+ * Returns the value of a string parameter.
+ * @param {String} p_prefix - The prefix of the parameter, usually the name of the bus/strip (ex: `Strip[1]`).
+ * @param {String} p_parameter - The name of the parameter (ex: `name`).
+ * __________
+ * @returns {String} - The value of the parameter.
+ * @throws {VMRError} - If the parameter is not found, or an internal error occurs.
+ */
+ static GetParameterString(p_prefix, p_parameter) {
+ local result, value := Buffer(1024)
+ try result := DllCall(VBVMR.FUNC.GetParameterStringW, "AStr", p_prefix . "." . p_parameter, "Ptr", value, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.GetParameterString.Name, p_prefix, p_parameter)
+ if (result < 0)
+ throw VMRError(result, VBVMR.GetParameterString.Name, p_prefix, p_parameter)
+ return StrGet(value, 512)
+ }
+ /**
+ * Returns the level of a single bus/strip channel.
+ * @param {Number} p_type - The type of the returned level
+ * - `0`: pre-fader
+ * - `1`: post-fader
+ * - `2`: post-mute
+ * - `3`: output-levels
+ * @param {Number} p_channel - The channel's zero-based index.
+ * - Channel Indices depend on the type of voiceemeeter running.
+ * - Channel Indices are incremented from the left to right (On the Voicemeeter UI), starting at `0`, Buses and Strips have separate Indices (see `p_type`).
+ * - Physical (hardware) strips have 2 channels (left, right), Buses and virtual strips have 8 channels.
+ * __________
+ * @returns {Number} - The level of the requested channel.
+ * @throws {VMRError} - If the channel index is invalid, or an internal error occurs.
+ */
+ static GetLevel(p_type, p_channel) {
+ local result, level := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.GetLevel, "Int", p_type, "Int", p_channel, "Ptr", level)
+ catch Error as err
+ throw VMRError(err, VBVMR.GetLevel.Name, p_type, p_channel)
+ if (result < 0)
+ return 0
+ return NumGet(level, 0, "Float")
+ }
+ /**
+ * Returns the type of Voicemeeter running.
+ * @see {@link VMR.Types|`VMR.Types`} for possible values.
+ * __________
+ * @returns {Number} - The type of Voicemeeter running.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static GetVoicemeeterType() {
+ local result, vtype := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.GetVoicemeeterType, "Ptr", vtype, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.GetVoicemeeterType.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.GetVoicemeeterType.Name)
+ return NumGet(vtype, 0, "Int")
+ }
+ /**
+ * Returns the version of Voicemeeter running.
+ * - The version is returned as a 4-part string (v1.v2.v3.v4)
+ * __________
+ * @returns {String} - The version of Voicemeeter running.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static GetVoicemeeterVersion() {
+ local result, version := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.GetVoicemeeterVersion, "Ptr", version, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.GetVoicemeeterVersion.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.GetVoicemeeterVersion.Name)
+ version := NumGet(version, 0, "Int")
+ local v1 := (version & 0xFF000000) >>> 24,
+ v2 := (version & 0x00FF0000) >>> 16,
+ v3 := (version & 0x0000FF00) >>> 8,
+ v4 := version & 0x000000FF
+ return Format("{:d}.{:d}.{:d}.{:d}", v1, v2, v3, v4)
+ }
+ /**
+ * Returns the number of Output Devices available on the system.
+ * __________
+ * @returns {Number} - The number of output devices.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Output_GetDeviceNumber() {
+ local result
+ try result := DllCall(VBVMR.FUNC.Output_GetDeviceNumber, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.Output_GetDeviceNumber.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Output_GetDeviceNumber.Name)
+ return result
+ }
+ /**
+ * Returns the Descriptor of an output device.
+ * @param {Number} p_index - The index of the device (zero-based).
+ * __________
+ * @returns {VMRDevice} - An object containing the `Name`, `Driver` and `Hwid` of the device.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Output_GetDeviceDesc(p_index) {
+ local result, name := Buffer(1024),
+ hwid := Buffer(1024),
+ driver := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.Output_GetDeviceDescW, "Int", p_index, "Ptr", driver, "Ptr", name, "Ptr", hwid, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.Output_GetDeviceDesc.Name, p_index)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Output_GetDeviceDesc.Name, p_index)
+ return VMRDevice(StrGet(name, 512), NumGet(driver, 0, "UInt"), StrGet(hwid, 512))
+ }
+ /**
+ * Returns the number of Input Devices available on the system.
+ * __________
+ * @returns {Number} - The number of input devices.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Input_GetDeviceNumber() {
+ local result
+ try result := DllCall(VBVMR.FUNC.Input_GetDeviceNumber, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.Input_GetDeviceNumber.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Input_GetDeviceNumber.Name)
+ return result
+ }
+ /**
+ * Returns the Descriptor of an input device.
+ * @param {Number} p_index - The index of the device (zero-based).
+ * __________
+ * @returns {VMRDevice} - An object containing the `Name`, `Driver` and `Hwid` of the device.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Input_GetDeviceDesc(p_index) {
+ local result, name := Buffer(1024),
+ hwid := Buffer(1024),
+ driver := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.Input_GetDeviceDescW, "Int", p_index, "Ptr", driver, "Ptr", name, "Ptr", hwid, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.Input_GetDeviceDesc.Name, p_index)
+ if (result < 0)
+ throw VMRError(result, VBVMR.Input_GetDeviceDesc.Name, p_index)
+ return VMRDevice(StrGet(name, 512), NumGet(driver, 0, "UInt"), StrGet(hwid, 512))
+ }
+ /**
+ * Checks if any parameters have changed.
+ * __________
+ * @returns {Number}
+ * - `0` : No change
+ * - `1` : Some parameters have changed
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static IsParametersDirty() {
+ local result
+ try result := DllCall(VBVMR.FUNC.IsParametersDirty)
+ catch Error as err
+ throw VMRError(err, VBVMR.IsParametersDirty.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.IsParametersDirty.Name)
+ return result
+ }
+ /**
+ * Returns the current status of a given button.
+ * @param {Number} p_logicalButton - The index of the button (zero-based).
+ * @param {Number} p_bitMode - The type of the returned value.
+ * - `0`: button-state
+ * - `2`: displayed-state
+ * - `3`: trigger-state
+ * __________
+ * @returns {Number} - The status of the button
+ * - `0`: Off
+ * - `1`: On
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static MacroButton_GetStatus(p_logicalButton, p_bitMode) {
+ local pValue := Buffer(4)
+ try errLevel := DllCall(VBVMR.FUNC.MacroButton_GetStatus, "Int", p_logicalButton, "Ptr", pValue, "Int", p_bitMode, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.MacroButton_GetStatus.Name, p_logicalButton, p_bitMode)
+ if (errLevel < 0)
+ throw VMRError(errLevel, VBVMR.MacroButton_GetStatus.Name, p_logicalButton, p_bitMode)
+ return NumGet(pValue, 0, "Float")
+ }
+ /**
+ * Sets the status of a given button.
+ * @param {Number} p_logicalButton - The index of the button (zero-based).
+ * @param {Number} p_value - The value to set.
+ * - `0`: Off
+ * - `1`: On
+ * @param {Number} p_bitMode - The type of the returned value.
+ * - `0`: button-state
+ * - `2`: displayed-state
+ * - `3`: trigger-state
+ * __________
+ * @returns {Number} - The status of the button
+ * - `0`: Off
+ * - `1`: On
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static MacroButton_SetStatus(p_logicalButton, p_value, p_bitMode) {
+ local result
+ try result := DllCall(VBVMR.FUNC.MacroButton_SetStatus, "Int", p_logicalButton, "Float", p_value, "Int", p_bitMode, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.MacroButton_SetStatus.Name, p_logicalButton, p_value, p_bitMode)
+ if (result < 0)
+ throw VMRError(result, VBVMR.MacroButton_SetStatus.Name, p_logicalButton, p_value, p_bitMode)
+ return p_value
+ }
+ /**
+ * Checks if any Macro Buttons states have changed.
+ * __________
+ * @returns {Number}
+ * - `0` : No change
+ * - `> 0` : Some buttons have changed
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static MacroButton_IsDirty() {
+ local result
+ try result := DllCall(VBVMR.FUNC.MacroButton_IsDirty)
+ catch Error as err
+ throw VMRError(err, VBVMR.MacroButton_IsDirty.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.MacroButton_IsDirty.Name)
+ return result
+ }
+ /**
+ * Returns any available MIDI messages from Voicemeeter's MIDI mapping.
+ * __________
+ * @returns {Array} - `[0xF0, 0xFF, ...]` An array of hex-formatted bytes that compose one or more MIDI messages, or an empty string `""` if no messages are available.
+ * - A single message is usually 2 or 3 bytes long
+ * - The returned array will contain at most `1024` bytes.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static GetMidiMessage() {
+ local result, data := Buffer(1024),
+ messages := []
+ try result := DllCall(VBVMR.FUNC.GetMidiMessage, "Ptr", data, "Int", 1024)
+ catch Error as err
+ throw VMRError(err, VBVMR.GetMidiMessage.Name)
+ if (result == -1)
+ throw VMRError(result, VBVMR.GetMidiMessage.Name)
+ if (result < 1)
+ return ""
+ loop (result) {
+ messages.Push(Format("0x{:X}", NumGet(data, A_Index - 1, "UChar")))
+ }
+ return messages
+ }
+ /**
+ * Sets one or more parameters using a voicemeeter script.
+ * @param {String} p_script - The script to execute (must be less than `48kb`).
+ * - Scripts can contain one or more parameter changes
+ * - Changes can be seperated by a new line, `;` or `,`.
+ * - Indices inside the script are zero-based.
+ * __________
+ * @returns {Number}
+ * - `0` : OK (no error)
+ * - `> 0` : Number of the line causing an error
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static SetParameters(p_script) {
+ local result
+ try result := DllCall(VBVMR.FUNC.SetParametersW, "WStr", p_script, "Int")
+ catch Error as err
+ throw VMRError(err, VBVMR.SetParameters.Name)
+ if (result < 0)
+ throw VMRError(result, VBVMR.SetParameters.Name)
+ return result
+ }
+}
+/**
+ * A basic wrapper for an async operation.
+ *
+ * This is needed because the VMR API is asynchronous which means that operations like `SetFloatParameter` do not take effect immediately,
+ * and so if the same parameter was fetched right after it was set, the old value would be returned (or sometimes it would return a completely invalid value).
+ *
+ * And unfortunately, the VMR API does not provide any meaningful way to wait for a particular operation to complete (callbacks, synchronous api), and so this class uses a normal timer to wait for the operation to complete.
+ */
+class VMRAsyncOp {
+ static DEFAULT_DELAY := 50
+ /**
+ * Creates a new async operation.
+ *
+ * @param {() => Any} p_supplier - (Optional) Supplies the result of the async operation.
+ * @param {Number} p_autoResolveTimeout - (Optional) Automatically resolves the async operation after the specified number of milliseconds.
+ */
+ __New(p_supplier?, p_autoResolveTimeout?) {
+ if (IsSet(p_supplier)) {
+ if !(p_supplier is Func)
+ throw VMRError("p_supplier must be a function.", this.__New.Name, p_supplier)
+ this._supplier := p_supplier
+ }
+ this._value := ""
+ this._listeners := []
+ this.IsEmpty := false
+ this.Resolved := false
+ if (IsSet(p_autoResolveTimeout) && IsNumber(p_autoResolveTimeout)) {
+ if (p_autoResolveTimeout = 0)
+ this._Resolve()
+ else
+ SetTimer(this._Resolve.Bind(this), -Abs(p_autoResolveTimeout))
+ }
+ }
+ /**
+ * Creates an empty async operation that's already been resolved.
+ * @type {VMRAsyncOp}
+ */
+ static Empty {
+ get {
+ local empty := VMRAsyncOp()
+ empty.IsEmpty := true
+ empty._Resolve()
+ return empty
+ }
+ }
+ /**
+ * Adds a listener to the async operation.
+ *
+ * @param {(Any) => Any} p_listener - A function that will be called when the async operation is resolved.
+ * @param {Number} p_innerOpDelay - (Optional) If passed, the returned async operation will be delayed by the specified number of milliseconds.
+ * __________
+ * @returns {VMRAsyncOp} - a new async operation that will be resolved when the current operation is resolved and the listener is called.
+ * @throws {VMRError} - if `p_listener` is not a function or has an invalid number of parameters.
+ */
+ Then(p_listener, p_innerOpDelay := 0) {
+ if !(p_listener is Func)
+ throw VMRError("p_listener must be a function.", this.Then.Name, p_listener)
+ if (p_listener.MinParams > 1)
+ throw VMRError("p_listener must require 0 or 1 parameters.", this.Then.Name, p_listener)
+ if (this.Resolved) {
+ local result := this._SafeCall(p_listener)
+ return VMRAsyncOp(() => result, p_innerOpDelay)
+ }
+ else {
+ local innerOp := VMRAsyncOp()
+ this._listeners.push({ func: p_listener, op: innerOp, delay: Abs(p_innerOpDelay) })
+ return innerOp
+ }
+ }
+ /**
+ * Waits for the async operation to be resolved.
+ *
+ * @param {Number} p_timeoutMs - (Optional) The maximum number of milliseconds to wait before throwing an error.
+ * __________
+ * @returns {Any} - The result of the async operation.
+ */
+ Await(p_timeoutMs := 0) {
+ if (this.Resolved)
+ return this._value
+ local currentMs := A_TickCount
+ while (!this.Resolved) {
+ if (p_timeoutMs > 0 && A_TickCount - currentMs > p_timeoutMs)
+ throw VMRError("The async operation timed out", this.Await.Name, p_timeoutMs)
+ Sleep(VMRAsyncOp.DEFAULT_DELAY)
+ }
+ return this._value
+ }
+ /**
+ * Resolves the async operation.
+ *
+ * @param {Any} p_value - (Optional) A value to resolve the async operation with, this will take precedence over the supplier.
+ */
+ _Resolve(p_value?) {
+ if (this.Resolved)
+ throw VMRError("This async operation has already been resolved.", this._Resolve.Name)
+ if (IsSet(p_value))
+ this._value := p_value
+ else if (this._supplier is Func)
+ this._value := this._supplier.Call()
+ ; If the supplier returned another async operation, resolve to the actual value.
+ if (this._value is VMRAsyncOp)
+ this._value := this._value.Await()
+ this.Resolved := true
+ for (listener in this._listeners) {
+ local value := this._SafeCall(listener.func)
+ , delay := listener.delay > 0 ? listener.delay : VMRAsyncOp.DEFAULT_DELAY
+ SetTimer(listener.op._Resolve.Bind(listener.op, value), -delay)
+ }
+ }
+ /**
+ * Calls the listener with the appropriate number of parameters and catches any thrown errors.
+ *
+ * @param {Func} p_listener - A function that will be called when the async operation is resolved.
+ * __________
+ * @returns {Any} - The result of the listener call.
+ */
+ _SafeCall(p_listener) {
try {
- ;sync vmr parameters
- isParametersDirty:= VBVMR.IsParametersDirty()
-
- ;sync macro buttons states
- isMacroButtonsDirty:= VBVMR.MacroButton_IsDirty()
-
- ;sync bus/strip level arrays
- loop % VBVMR.BUSCOUNT {
- this.bus[A_Index].__updateLevel()
+ if (p_listener.MaxParams = 0) {
+ return p_listener.Call()
}
- loop % VBVMR.STRIPCOUNT {
- this.strip[A_Index].__updateLevel()
- }
-
- ;sync successful
- ignore_msg:=0
-
- ;level callback
- if(this.onUpdateLevels){
- this.onUpdateLevels.Call()
- }
-
- ;parameter callback
- if(isParametersDirty && this.onUpdateParameters){
- this.onUpdateParameters.Call()
- }
-
- ;macrobutton callback
- if(isMacroButtonsDirty && this.onUpdateMacrobuttons){
- this.onUpdateMacrobuttons.Call()
- }
-
- ;midi callback
- if(this.onMidiMessage && midiMessages:= VBVMR.GetMidiMessage()){
- this.onMidiMessage.Call(midiMessages)
- }
- return isParametersDirty || isMacroButtonsDirty || midiMessages
- } catch e {
- if(!ignore_msg){
- MsgBox, 52, VMR.ahk, % Format("An error occurred during synchronization: {}`nAttempt to restart VoiceMeeter?", e.Message), 10
- IfMsgBox Yes
- this.runVoicemeeter(VBVMR.VM_TYPE)
- IfMsgBox, No
- ignore_msg:=1
- IfMsgBox, Timeout
- ignore_msg:=1
- sleep, 1000
+ else if (p_listener.MinParams < 2) {
+ return p_listener.Call(this._value)
}
}
}
-
- __onExit(){
- while(this.__syncWithDLL()){
+}
+/**
+ * A base class for {@link VMRBus|`VMRBus`} and {@link VMRStrip|`VMRStrip`}
+ */
+class VMRAudioIO {
+ static IS_CLASS_INIT := false
+ /**
+ * The object's upper gain limit
+ * @type {Number}
+ *
+ * Setting the gain above the limit will reset it to this value.
+ */
+ GainLimit := VMRConsts.AUDIO_IO_GAIN_MAX
+ /**
+ * Gets/Sets the gain as a percentage
+ * @type {Number} - The gain as a percentage (e.g. `44` = 44%)
+ *
+ * @example
+ * local gain := vm.Bus[1].GainPercentage ; get the gain as a percentage
+ * vm.Bus[1].GainPercentage++ ; increases the gain by 1%
+ */
+ GainPercentage {
+ get => VMRUtils.DbToPercentage(this.GetParameter("gain"))
+ set => this.SetParameter("gain", VMRUtils.PercentageToDb(Value))
+ }
+ /**
+ * Set/Get the object's EQ parameters.
+ *
+ * @type {Number} - The EQ parameter's value.
+ * @param {Array} p_params - An array containing the EQ parameter name and the channel/cell numbers.
+ *
+ * - Bus EQ parameters: `EQ[param] := value`
+ * - EQ channel/cells parameters: `EQ[param, channel, cell] := value`
+ *
+ * @example
+ * vm.Bus[1].EQ["gain", 1, 1] := -6
+ * vm.Bus[1].EQ["q", 1, 1] := 90
+ * vm.Bus[1].EQ["AB"] := true
+ */
+ EQ[p_params*] {
+ get {
+ if (p_params.Length == 3)
+ this.GetParameter("EQ.channel[" p_params[2] - 1 "].cell[" p_params[3] - 1 "]." p_params[1])
+ else
+ this.GetParameter("EQ." p_params[1])
}
- Sleep, 100 ; to make sure all commands are executed before exiting
- VBVMR.Logout()
- DllCall("FreeLibrary", "Ptr", VBVMR.DLL)
- }
-
- class BusStrip {
- static BUS_COUNT:=0
- , BUS_LEVEL_COUNT:=0
- , BusDevices:=Array()
- , STRIP_COUNT:=0
- , STRIP_LEVEL_COUNT:=0
- , StripDevices:=Array()
- , BUS_STRIP_NAMES:=
- ( Join LTrim ; ahk
- {
- 1: {
- "Bus": [
- "A",
- "B"
- ],
- "Strip": [
- "Input #1",
- "Input #2",
- "Virtual Input #1"
- ]
- },
- 2: {
- "Bus": [
- "A1",
- "A2",
- "A3",
- "B1",
- "B2"
- ],
- "Strip": [
- "Input #1",
- "Input #2",
- "Input #3",
- "Virtual Input #1",
- "Virtual Input #2"
- ]
- },
- 3: {
- "Bus": [
- "A1",
- "A2",
- "A3",
- "A4",
- "A5",
- "B1",
- "B2",
- "B3"
- ],
- "Strip": [
- "Input #1",
- "Input #2",
- "Input #3",
- "Input #4",
- "Input #5",
- "Virtual Input #1",
- "Virtual Input #2",
- "Virtual Input #3"
- ]
- }
- }
- )
- , initiated:=0
-
- __Set(p_name, p_value, p_sec_value:=""){
- if(VMR.BusStrip.initiated && this.BUS_STRIP_ID){
- switch p_name {
- case "gain":
- return Format("{:.1f}",this.setParameter(p_name, max(-60.0, min(p_value, this.gain_limit))))
- case "limit":
- return Format("{:.1f}",this.setParameter(p_name, max(-40.0, min(p_value, 12.0))))
- case "device":
- if(IsObject(p_value))
- return this.__setDevice(p_value)
- if(!p_value)
- return this.__setDevice({name:"",driver:"wdm"})
- driver:= p_sec_value? p_value : "wdm"
- name:= p_sec_value? p_sec_value : p_value
- return this.__setDevice(this.__getDeviceObj(name,driver))
- case "mute":
- if(p_value = -1)
- p_value:= !this.mute
- }
- return this.setParameter(p_name,p_value)
+ set {
+ if (p_params.Length == 3)
+ this.SetParameter("EQ.channel[" p_params[2] - 1 "].cell[" p_params[3] - 1 "]." p_params[1], Value)
+ else
+ this.SetParameter("EQ." p_params[1], Value)
+ }
+ }
+ /**
+ * Gets/Sets the object's current device
+ *
+ * @type {VMRDevice} - The device object.
+ * - When setting the device, either a device name or a device object can be passed, the latter can be retrieved using `VMRStrip`/`VMRBus` `GetDevice()` methods.
+ *
+ * @param {String} p_driver - (Optional) The driver of the device (ex: `wdm`)
+ *
+ * @example
+ * vm.Bus[1].Device := VMRBus.GetDevice("Headphones") ; using a substring of the device name
+ * vm.Bus[1].Device := "Headphones (Virtual Audio Device)" ; using a device's full name
+ */
+ Device[p_driver?] {
+ get {
+ local devices := this.Type == "Bus" ? VMRBus.Devices : VMRStrip.Devices
+ ; TODO: Once Voicemeeter adds support for getting the type (driver) of the current device, we can ignore the p_driver parameter
+ return this._MatchDevice(this.GetParameter("device.name"), p_driver ?? unset)
+ }
+ set {
+ local deviceName := Value, deviceDriver := p_driver ?? VMRConsts.DEFAULT_DEVICE_DRIVER
+ ; Allow setting the device using a device object
+ if (IsObject(Value)) {
+ deviceDriver := Value.Driver
+ deviceName := Value.Name
}
+ if (VMRUtils.IndexOf(VMRConsts.DEVICE_DRIVERS, deviceDriver) == -1)
+ throw VMRError(deviceDriver " is not a valid device driver", "Device", p_driver, Value)
+ this.SetParameter("device." deviceDriver, deviceName)
}
-
- __Get(p_name){
- if(VMR.BusStrip.initiated && this.BUS_STRIP_ID){
- switch p_name {
- case "gain","limit":
- return Format("{:.1f}",this.getParameter(p_name))
- case "device":
- return this.getParameter("device.name")
- }
- return this.getParameter(p_name)
+ }
+ /**
+ * An array of the object's channel levels
+ * @type {Array}
+ *
+ * Physical (hardware) strips have 2 channels (left, right), Buses and virtual strips have 8 channels
+ * __________
+ * @example
Get the current peak level of a bus
+ * local peakLevel := Max(vm.Bus[1].Level*)
+ */
+ Level := Array()
+ /**
+ * The object's identifier that's used when calling VMR's functions.
+ * Like `Bus[0]` or `Strip[3]`
+ *
+ * @type {String}
+ */
+ Id := ""
+ /**
+ * The object's one-based index
+ * @type {Number}
+ */
+ Index := 0
+ /**
+ * The object's type (`Bus` or `Strip`)
+ * @type {String}
+ */
+ Type := ""
+ /**
+ * Creates a new `VMRAudioIO` object.
+ * @param {Number} p_index - The zero-based index of the bus/strip.
+ * @param {String} p_ioType - The type of the object. (`Bus` or `Strip`)
+ */
+ __New(p_index, p_ioType) {
+ this._index := p_index
+ this._isPhysical := false
+ this.Id := p_ioType "[" p_index "]"
+ this.Index := p_index + 1
+ this.Type := p_ioType
+ }
+ /**
+ * @private - Internal method
+ * @description Implements a default property getter, this is invoked when using the object access syntax.
+ * @example
+ * local sampleRate := bus.device["sr"]
+ * MsgBox("Gain is " bus.gain)
+ *
+ * @param {String} p_key - The name of the parameter.
+ * @param {Array} p_params - An extra param passed when using bracket syntax with a normal prop access (`bus.device["sr"]`).
+ * __________
+ * @returns {Any} The value of the parameter.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ _Get(p_key, p_params) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return ""
+ if (p_params.Length > 0) {
+ for param in p_params {
+ p_key .= IsNumber(param) ? "[" param - 1 "]" : "." param
}
}
-
- __New(p_type){
- this.BUS_STRIP_TYPE := p_type
- this.level := Array()
- this.LEVEL_INDEX := Array()
- this.gain_limit:= 12.0
- if (p_type="Strip") {
- this.BUS_STRIP_INDEX := VMR.BusStrip.STRIP_COUNT++
- loop % this.isPhysical() ? 2 : 8
- this.LEVEL_INDEX.Push(VMR.BusStrip.STRIP_LEVEL_COUNT++)
- }else{
- this.BUS_STRIP_INDEX := VMR.BusStrip.BUS_COUNT++
- loop 8
- this.LEVEL_INDEX.Push(VMR.BusStrip.BUS_LEVEL_COUNT++)
+ return this.GetParameter(p_key)
+ }
+ /**
+ * @private - Internal method
+ * @description Implements a default property setter, this is invoked when using the object access syntax.
+ * @example
+ * bus.gain := 0.5
+ * bus.device["mme"] := "Headset"
+ *
+ * @param {String} p_key - The name of the parameter.
+ * @param {Array} p_params - An extra param passed when using bracket syntax with a normal prop access. `bus.device["wdm"] := "Headset"`
+ * @param {Any} p_value - The value of the parameter.
+ * __________
+ * @returns {Boolean} - `true` if the parameter was set successfully.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ _Set(p_key, p_params, p_value) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return false
+ if (p_params.Length > 0) {
+ for param in p_params {
+ p_key .= IsNumber(param) ? "[" param - 1 "]" : "." param
}
- this.BUS_STRIP_ID := this.BUS_STRIP_TYPE . "[" . this.BUS_STRIP_INDEX . "]"
- this.name := VMR.BusStrip.BUS_STRIP_NAMES[VBVMR.VM_TYPE][this.BUS_STRIP_TYPE][this.BUS_STRIP_INDEX+1]
- }
-
- getGainPercentage(){
- return Format("{:.2f}",this.getPercentage(this.gain))
- }
-
- setGainPercentage(percentage){
- return this.gain := this.getdB(percentage)
- }
-
- getPercentage(dB){
- min_s := 10**(-60/20), max_s := 10**(0/20)
- return ((10**(dB/20))-min_s)/(max_s-min_s)*100
- }
-
- getdB(percentage){
- min_s := 10**(-60/20), max_s := 10**(0/20)
- return 20*Log(min_s + percentage/100*(max_s-min_s))
- }
-
- setParameter(parameter, value){
- local func
- if parameter contains device,FadeTo,Label,FadeBy,AppGain,AppMute
- func:= "setParameterString"
+ }
+ return !this.SetParameter(p_key, p_value).IsEmpty
+ }
+ /**
+ * Implements a default indexer.
+ * this is invoked when using the bracket access syntax.
+ * @example
+ * MsgBox(strip["mute"])
+ * bus["gain"] := 0.5
+ *
+ * @param {String} p_key - The name of the parameter.
+ * __________
+ * @type {Any} - The value of the parameter.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ __Item[p_key] {
+ get => this.GetParameter(p_key)
+ set => this.SetParameter(p_key, Value)
+ }
+ /**
+ * Sets the value of a parameter.
+ *
+ * @param {String} p_name - The name of the parameter.
+ * @param {Any} p_value - The value of the parameter.
+ * __________
+ * @returns {VMRAsyncOp} - An async operation that resolves to `true` if the parameter was set successfully.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ SetParameter(p_name, p_value) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return VMRAsyncOp.Empty
+ local vmrFunc := VMRAudioIO._IsStringParam(p_name)
+ ? VBVMR.SetParameterString.Bind(VBVMR)
+ : VBVMR.SetParameterFloat.Bind(VBVMR)
+ if (p_name = "gain") {
+ p_value := VMRUtils.EnsureBetween(p_value, VMRConsts.AUDIO_IO_GAIN_MIN, this.GainLimit)
+ }
+ else if (p_name = "limit") {
+ p_value := VMRUtils.EnsureBetween(p_value, VMRConsts.AUDIO_IO_LIMIT_MIN, VMRConsts.AUDIO_IO_LIMIT_MAX)
+ }
+ else if (p_name = "mute") {
+ p_value := p_value < 0 ? !this.GetParameter("mute") : p_value
+ }
+ local result := vmrFunc.Call(this.Id, p_name, p_value)
+ return VMRAsyncOp(() => result == 0, 50)
+ }
+ /**
+ * Returns the value of a parameter.
+ *
+ * @param {String} p_name - The name of the parameter.
+ * __________
+ * @returns {Any} - The value of the parameter.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ GetParameter(p_name) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return -1
+ local vmrFunc := VMRAudioIO._IsStringParam(p_name) ? VBVMR.GetParameterString.Bind(VBVMR) : VBVMR.GetParameterFloat.Bind(VBVMR)
+ switch p_name, false {
+ case "gain", "limit":
+ return Format("{:.2f}", vmrFunc.Call(this.Id, p_name))
+ case "device":
+ p_name := "device.name"
+ }
+ return vmrFunc.Call(this.Id, p_name)
+ }
+ /**
+ * Increments a parameter by a specific amount.
+ * - It's recommended to use this method instead of incrementing the parameter directly (`++vm.Bus[1].Gain`).
+ * - Since this method doesn't fetch the current value of the parameter to update it, {@link @VMRAudioIO.GainLimit|`GainLimit`} cannot be applied here.
+ *
+ * @param {String} p_param - The name of the parameter, must be a numeric parameter (see {@link VMRConsts.IO_STRING_PARAMETERS|`VMRConsts.IO_STRING_PARAMETERS`}).
+ * @param {Number} p_amount - The amount to increment the parameter by, can be set to a negative value to decrement instead.
+ * __________
+ * @returns {VMRAsyncOp} - An async operation that resolves with the incremented value.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ * __________
+ * @example
usage with callbacks
+ * vm.Bus[1].Increment("gain", 1).Then(val => Tooltip(val)) ; increases the gain by 1dB
+ * vm.Bus[1].Increment("gain", -5).Then(val => Tooltip(val)) ; decreases the gain by 5dB
+ *
+ * @example
"synchronous" usage
+ * ; increases the gain by 1dB and waits for the operation to complete
+ * ; this is equivalent to `vm.Bus[1].Gain++` followed by `Sleep(50)`
+ * gainValue := vm.Bus[1].Increment("gain", 1).Await()
+ *
+ */
+ Increment(p_param, p_amount) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return VMRAsyncOp.Empty
+ if (!IsNumber(p_amount))
+ throw VMRError("p_amount must be a number", this.Increment.Name, p_param, p_amount)
+ if (VMRAudioIO._IsStringParam(p_param))
+ throw VMRError("p_param must be a numeric parameter", this.Increment.Name, p_param, p_amount)
+ local script := Format("{}.{} {} {}", this.Id, p_param, p_amount < 0 ? "-=" : "+=", Abs(p_amount))
+ VBVMR.SetParameters(script)
+ return VMRAsyncOp(() => this.GetParameter(p_param), 50)
+ }
+ /**
+ * Sets the gain to a specific value with a progressive fade.
+ *
+ * @param {Number} p_db - The gain value in dBs.
+ * @param {Number} p_duration - The duration of the fade in milliseconds.
+ * __________
+ * @returns {VMRAsyncOp} - An async operation that resolves with the final gain value.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ FadeTo(p_db, p_duration) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return VMRAsyncOp.Empty
+ if (this.SetParameter("FadeTo", "(" p_db ", " p_duration ")").IsEmpty)
+ return VMRAsyncOp.Empty
+ return VMRAsyncOp(() => this.GetParameter("gain"), p_duration + 50)
+ }
+ /**
+ * Fades the gain by a specific amount.
+ *
+ * @param {Number} p_dbAmount - The amount to fade the gain by in dBs.
+ * @param {Number} p_duration - The duration of the fade in milliseconds.
+ * _________
+ * @returns {VMRAsyncOp} - An async operation that resolves with the final gain value.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ FadeBy(p_dbAmount, p_duration) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return VMRAsyncOp.Empty
+ if (!this.SetParameter("FadeBy", "(" p_dbAmount ", " p_duration ")"))
+ return VMRAsyncOp.Empty
+ return VMRAsyncOp(() => this.GetParameter("gain"), p_duration + 50)
+ }
+ /**
+ * Returns `true` if the bus/strip is a physical (hardware) one.
+ * __________
+ * @returns {Boolean}
+ */
+ IsPhysical() => this._isPhysical
+ /**
+ * @private - Internal method
+ * @description Returns `true` if the parameter is a string parameter.
+ *
+ * @param {String} p_param - The name of the parameter.
+ * @returns {Boolean}
+ */
+ static _IsStringParam(p_param) => VMRUtils.IndexOf(VMRConsts.IO_STRING_PARAMETERS, p_param) > 0
+ /**
+ * @private - Internal method
+ * @description Returns a device object.
+ *
+ * @param {Array} p_devicesArr - An array of {@link VMRDevice|`VMRDevice`} objects.
+ * @param {String} p_name - The name of the device.
+ * @param {String} p_driver - The driver of the device.
+ * @see {@link VMRConsts.DEVICE_DRIVERS|`VMRConsts.DEVICE_DRIVERS`} for a list of valid drivers.
+ * __________
+ * @returns {VMRDevice} - A device object, or an empty string `""` if the device was not found.
+ */
+ static _GetDevice(p_devicesArr, p_name, p_driver?) {
+ local device, index
+ if (!IsSet(p_driver))
+ p_driver := VMRConsts.DEFAULT_DEVICE_DRIVER
+ for (index, device in p_devicesArr) {
+ if (device.driver = p_driver && InStr(device.name, p_name))
+ return device.Clone()
+ }
+ return ""
+ }
+ /**
+ * @private - Internal method
+ * @description Returns a device object that exactly matches the specified name.
+ * @param {String} p_name - The name of the device.
+ * __________
+ * @returns {VMRDevice}
+ */
+ _MatchDevice(p_name, p_driver?) {
+ local devices := this.Type == "Bus" ? VMRBus.Devices : VMRStrip.Devices
+ for device in devices {
+ if (device.name == p_name && (!IsSet(p_driver) || device.driver = p_driver))
+ return device.Clone()
+ }
+ return ""
+ }
+}
+/**
+ * A wrapper class for voicemeeter buses.
+ * @extends {VMRAudioIO}
+ */
+class VMRBus extends VMRAudioIO {
+ static LEVELS_COUNT := 0
+ /**
+ * An array of bus (output) devices
+ * @type {Array} - An array of {@link VMRDevice} objects.
+ */
+ static Devices := Array()
+ /**
+ * The bus's name (as shown in voicemeeter's UI)
+ *
+ * @type {String}
+ *
+ * @example
+ * local busName := VMRBus.Bus[1].Name ; "A1" or "A" depending on voicemeeter's type
+ */
+ Name := ""
+ /**
+ * Creates a new VMRBus object.
+ * @param {Number} p_index - The zero-based index of the bus.
+ * @param {Number} p_vmrType - The type of the running voicemeeter.
+ */
+ __New(p_index, p_vmrType) {
+ super.__New(p_index, "Bus")
+ this._channelCount := 8
+ this.Name := VMRConsts.BUS_NAMES[p_vmrType][p_index + 1]
+ switch p_vmrType {
+ case 1:
+ super._isPhysical := true
+ case 2:
+ super._isPhysical := this._index < 3
+ case 3:
+ super._isPhysical := this._index < 5
+ }
+ ; Setup the bus's levels array
+ this.Level.Length := this._channelCount
+ ; A bus's level index starts at the current total count
+ this._levelIndex := VMRBus.LEVELS_COUNT
+ VMRBus.LEVELS_COUNT += this._channelCount
+ this.DefineProp("__Get", { Call: super._Get })
+ this.DefineProp("__Set", { Call: super._Set })
+ }
+ _UpdateLevels() {
+ loop this._channelCount {
+ local vmrIndex := this._levelIndex + A_Index - 1
+ local level := Round(20 * Log(VBVMR.GetLevel(3, vmrIndex)))
+ this.Level[A_Index] := VMRUtils.EnsureBetween(level, -999, 999)
+ }
+ }
+ /**
+ * Retrieves a bus (output) device by its name/driver.
+ * @param {String} p_name - The name of the device.
+ * @param {String} p_driver - (Optional) The driver of the device, If omitted, {@link VMRConsts.DEFAULT_DEVICE_DRIVER|`VMRConsts.DEFAULT_DEVICE_DRIVER`} will be used.
+ * @see {@link VMRConsts.DEVICE_DRIVERS|`VMRConsts.DEVICE_DRIVERS`} for a list of valid drivers.
+ * __________
+ * @returns {VMRDevice} - A device object, or an empty string `""` if the device was not found.
+ */
+ static GetDevice(p_name, p_driver?) => VMRAudioIO._GetDevice(VMRBus.Devices, p_name, p_driver ?? unset)
+}
+/**
+ * A wrapper class for voicemeeter strips.
+ * @extends {VMRAudioIO}
+ */
+class VMRStrip extends VMRAudioIO {
+ static LEVELS_COUNT := 0
+ /**
+ * An array of strip (input) devices
+ * @type {Array} - An array of {@link VMRDevice} objects.
+ */
+ static Devices := Array()
+ /**
+ * The strip's name (as shown in voicemeeter's UI)
+ *
+ * @example
+ * local stripName := VMRBus.Strip[1].Name ; "Input #1"
+ *
+ * @readonly
+ * @type {String}
+ */
+ Name := ""
+ /**
+ * Sets an application's gain on the strip.
+ *
+ * @param {String|Number} p_app - The name of the application, or its one-based index.
+ * @type {Number} - The application's gain (`0.0` to `1.0`).
+ * __________
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ AppGain[p_app] {
+ set {
+ if (IsNumber(p_app))
+ this.SetParameter("App[" p_app - 1 "].Gain", VMRUtils.EnsureBetween(Round(Value, 2), 0.0, 1.0))
else
- func:= "setParameterFloat"
- return (VBVMR)[func](this.BUS_STRIP_ID, parameter, value)
- }
-
- getParameter(parameter){
- local func
- if parameter contains device,FadeTo,Label,FadeBy,AppGain,AppMute
- func:= "getParameterString"
+ this.SetParameter("AppGain", "(`"" p_app "`", " VMRUtils.EnsureBetween(Round(Value, 2), 0.0, 1.0) ")")
+ }
+ }
+ /**
+ * Sets an application's mute state on the strip.
+ *
+ * @param {String|Number} p_app - The name of the application, or its one-based index.
+ * @type {Boolean} - The application's mute state.
+ * __________
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ AppMute[p_app] {
+ set {
+ if (IsNumber(p_app))
+ this.SetParameter("App[" p_app - 1 "].Mute", Value)
else
- func:= "getParameterFloat"
- return (VBVMR)[func](this.BUS_STRIP_ID, parameter)
+ this.SetParameter("AppMute", "(`"" p_app "`", " Value ")")
}
-
- ; Returns 1 if the bus/strip is a physical one (Hardware bus/strip), 0 otherwise
- isPhysical(){
- Switch VBVMR.VM_TYPE {
- case 1:
- if(this.BUS_STRIP_TYPE = "Strip")
- return this.BUS_STRIP_INDEX < 2
- else
- return 1
- case 2:
- return this.BUS_STRIP_INDEX < 3
- case 3:
- return this.BUS_STRIP_INDEX < 5
- }
+ }
+ /**
+ * Creates a new VMRStrip object.
+ * @param {Number} p_index - The zero-based index of the strip.
+ * @param {Number} p_vmrType - The type of the running voicemeeter.
+ */
+ __New(p_index, p_vmrType) {
+ super.__New(p_index, "Strip")
+ this.Name := VMRConsts.STRIP_NAMES[p_vmrType][this.Index]
+ switch p_vmrType {
+ case 1:
+ super._isPhysical := this._index < 2
+ case 2:
+ super._isPhysical := this._index < 3
+ case 3:
+ super._isPhysical := this._index < 5
}
-
- __setDevice(device){
- if (!this.isPhysical())
- return -4
- if device.driver not in wdm,mme,ks,asio
- return -5
- return this.setParameter("device." . device.driver,device.name)
- }
-
- __getDeviceObj(substring,driver){
- local devices:= VMR.BusStrip[this.BUS_STRIP_TYPE . "Devices"]
- for i in devices
- if (devices[i].driver = driver && InStr(devices[i].name, substring))
- return devices[i]
- return {name:"",driver:"wdm"}
- }
-
- __updateLevel(){
- local type := this.BUS_STRIP_TYPE="Bus" ? 3 : 1
- loop % this.LEVEL_INDEX.Length() {
- level := VBVMR.GetLevel(type, this.LEVEL_INDEX[A_Index])
- this.level[A_Index] := Max(Round(20 * Log(level)), -999)
- }
+ ; physical strips have 2 channels, virtual strips have 8
+ this._channelCount := this.IsPhysical() ? 2 : 8
+ ; Setup the strip's levels array
+ this.Level.Length := this._channelCount
+ ; A strip's level index starts at the current total count
+ this._levelIndex := VMRStrip.LEVELS_COUNT
+ VMRStrip.LEVELS_COUNT += this._channelCount
+ this.DefineProp("__Get", { Call: super._Get })
+ this.DefineProp("__Set", { Call: super._Set })
+ }
+ _UpdateLevels() {
+ loop this._channelCount {
+ local vmrIndex := this._levelIndex + A_Index - 1
+ local level := Round(20 * Log(VBVMR.GetLevel(1, vmrIndex)))
+ this.Level[A_Index] := VMRUtils.EnsureBetween(level, -999, 999)
}
}
-
-
- class FXBase {
-
- reverb(onOff := -2) {
- switch (onOff) {
- case -2: ;getParam
- return VBVMR.GetParameterFloat("Fx.Reverb", "on")
- case -1: ;invert state
- onOff := !VBVMR.GetParameterFloat("Fx.Reverb", "on")
- }
- return VBVMR.SetParameterFloat("Fx.Reverb","on", onOff)
- }
-
- delay(onOff := -2) {
- switch (onOff) {
- case -2: ;getParam
- return VBVMR.GetParameterFloat("Fx.delay", "on")
- case -1: ;invert state
- onOff := !VBVMR.GetParameterFloat("Fx.delay", "on")
- }
- return VBVMR.SetParameterFloat("Fx.delay","on", onOff)
- }
- }
-
-
- ; These are read-only commands
- class Command {
-
- restart(){
- return VBVMR.SetParameterFloat("Command","Restart",1)
- }
-
- shutdown(){
- return VBVMR.SetParameterFloat("Command","Shutdown",1)
- }
-
- show(open := 1){
- return VBVMR.SetParameterFloat("Command","Show",open)
- }
-
- lock(state := 1){
- return VBVMR.SetParameterFloat("Command","Lock",state)
- }
-
- eject(){
- return VBVMR.SetParameterFloat("Command","Eject",1)
- }
-
- reset(){
- return VBVMR.SetParameterFloat("Command","Reset",1)
- }
-
- save(filePath){
- return VBVMR.SetParameterString("Command","Save",filePath)
- }
-
- load(filePath){
- return VBVMR.SetParameterString("Command","Load",filePath)
- }
-
- showVBANChat(show := 1) {
- return VBVMR.SetParameterFloat("Command","dialogshow.VBANCHAT",show)
- }
-
- state(buttonNum, newState) {
- return VBVMR.SetParameterFloat("Command.Button[" . buttonNum . "]", "State", newState)
- }
-
- stateOnly(buttonNum, newState) {
- return VBVMR.SetParameterFloat("Command.Button[" . buttonNum . "]", "stateOnly", newState)
- }
-
- trigger(buttonNum, newState) {
- return VBVMR.SetParameterFloat("Command.Button[" . buttonNum . "]", "trigger", newState)
- }
-
- saveBusEQ(busIndex, filePath) {
- return VBVMR.SetParameterFloat("Command","SaveBUSEQ[" busIndex "]", filePath)
- }
-
- loadBusEQ(busIndex, filePath) {
- return VBVMR.SetParameterFloat("Command","LoadBUSEQ[" busIndex "]", filePath)
- }
- }
-
-
- class VBAN {
- static instream:=""
- , outstream:=""
-
- enable{
- set{
- return VBVMR.SetParameterFloat("vban", "Enable", value)
- }
- get{
- return VBVMR.GetParameterFloat("vban", "Enable")
- }
+ /**
+ * Retrieves a strip (input) device by its name/driver.
+ * @param {String} p_name - The name of the device.
+ * @param {String} p_driver - (Optional) The driver of the device, If omitted, {@link VMRConsts.DEFAULT_DEVICE_DRIVER|`VMRConsts.DEFAULT_DEVICE_DRIVER`} will be used.
+ * @see {@link VMRConsts.DEVICE_DRIVERS|`VMRConsts.DEVICE_DRIVERS`} for a list of valid drivers.
+ * __________
+ * @returns {VMRDevice} - A device object, or an empty string `""` if the device was not found.
+ */
+ static GetDevice(p_name, p_driver?) => VMRAudioIO._GetDevice(VMRStrip.Devices, p_name, p_driver ?? unset)
+}
+/**
+ * Write-only actions that control voicemeeter
+ */
+class VMRCommands {
+ /**
+ * Restarts the Audio Engine
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Restart() => VBVMR.SetParameterFloat("Command", "Restart", true) == 0
+ /**
+ * Shuts down Voicemeeter
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Shutdown() => VBVMR.SetParameterFloat("Command", "Shutdown", true) == 0
+ /**
+ * Shows the Voicemeeter window
+ *
+ * @param {Boolean} p_open - (Optional) `true` to show the window, `false` to hide it
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Show(p_open := true) => VBVMR.SetParameterFloat("Command", "Show", p_open) == 0
+ /**
+ * Locks the Voicemeeter UI
+ *
+ * @param {number} p_state - (Optional) `true` to lock the UI, `false` to unlock it
+ * _________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Lock(p_state := true) => VBVMR.SetParameterFloat("Command", "Lock", p_state) == 0
+ /**
+ * Ejects the recorder's cassette
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Eject() => VBVMR.SetParameterFloat("Command", "Eject", true) == 0
+ /**
+ * Resets all voicemeeeter configuration
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Reset() => VBVMR.SetParameterFloat("Command", "Reset", true) == 0
+ /**
+ * Saves the current configuration to a file
+ *
+ * @param {String} p_filePath - The path to save the configuration to
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Save(p_filePath) => VBVMR.SetParameterString("Command", "Save", p_filePath) == 0
+ /**
+ * Loads configuration from a file
+ *
+ * @param {String} p_filePath - The path to load the configuration from
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ Load(p_filePath) => VBVMR.SetParameterString("Command", "Load", p_filePath) == 0
+ /**
+ * Shows the VBAN chat dialog
+ *
+ * @param {Boolean} p_show - (Optional) `true` to show the dialog, `false` to hide it
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ ShowVBANChat(p_show := true) => VBVMR.SetParameterFloat("Command", "dialogshow.VBANCHAT", p_show) == 0
+ /**
+ * Sets a macro button's parameter
+ *
+ * @param {Array} p_params - An array containing the button's one-based index and parameter name.
+ * @example
+ * vm.Command.Button[1, "State"] := 1
+ * vm.Command.Button[1, "Trigger"] := false
+ * vm.Command.Button[1, "Color"] := 8
+ */
+ Button[p_params*] {
+ set {
+ if (p_params.length() != 2)
+ throw VMRError("Invalid number of parameters for Command.Button[]", "Command.Button[]", p_params*)
+ return VBVMR.SetParameterFloat("Command.Button[" . (p_params[1] - 1) . "]", p_params[2], Value) == 0
}
-
- init(){
- VMR.VBAN.instream:= Array()
- VMR.VBAN.outstream:= Array()
- loop % VBVMR.VBANINCOUNT
- VMR.VBAN.instream.Push(new VMR.VBAN.Stream("in", A_Index))
- loop % VBVMR.VBANOUTCOUNT
- VMR.VBAN.outstream.Push(new VMR.VBAN.Stream("out", A_Index))
- }
-
- class Stream{
- static initiated:= 0
- __New(p_type,p_index){
- this.PARAM_PREFIX:= Format("vban.{}stream[{}]", p_type, p_index)
- }
- __Set(p_name,p_value){
- if(VMR.VBAN.stream.initiated) {
- if p_name contains name, ip
- return VBVMR.SetParameterString(this.PARAM_PREFIX, p_name, p_value)
- return VBVMR.SetParameterFloat(this.PARAM_PREFIX, p_name, p_value)
- }
-
- }
- __Get(p_name){
- if(VMR.VBAN.stream.initiated){
- if p_name contains name, ip
- return VBVMR.GetParameterString(this.PARAM_PREFIX, p_name)
- return VBVMR.GetParameterFloat(this.PARAM_PREFIX, p_name)
- }
- }
- }
- }
-
-
- class MacroButton {
-
- run(){
- Run, % VBVMR.DLL_PATH "\VoicemeeterMacroButtons.exe", % VBVMR.DLL_PATH, UseErrorLevel
- if(ErrorLevel)
- Throw, Exception("Could not run MacroButtons")
- }
-
- show(state := 1){
- if(state)
- WinShow, ahk_exe VoicemeeterMacroButtons.exe
- else
- WinHide, ahk_exe VoicemeeterMacroButtons.exe
- }
-
- setStatus(buttonIndex, newStatus, bitmode:=0) {
- return VBVMR.MacroButton_SetStatus(buttonIndex, newStatus, bitmode)
- }
-
- getStatus(buttonIndex, bitmode:=0){
- return VBVMR.MacroButton_GetStatus(buttonIndex, bitmode)
- }
-
- }
-
-
-
- class PatchBase {
- __Set(p_name, p_value){
- return VBVMR.SetParameterFloat("Patch", p_name, p_value)
- }
-
- __Get(p_name){
- return VBVMR.GetParameterFloat("Patch", p_name)
- }
- }
-
-
- class OptionBase {
- __Set(p_name, p_value){
- return VBVMR.SetParameterFloat("Option", p_name, p_value)
- }
-
- __Get(p_name){
- return VBVMR.GetParameterFloat("Option", p_name)
- }
-
- delay(busNum, p_delay := "") {
- ; in keeping with the 1 indexed class...
- busNum := busNum - 1
- if(p_delay == "") {
- ; get the value
- return VBVMR.GetParameterFloat("Option", "delay[" . busNum . "]")
+ }
+ /**
+ * Saves a bus's EQ settings to a file
+ *
+ * @param {Number} p_busIndex - The one-based index of the bus to save
+ * @param {String} p_filePath - The path to save the EQ settings to
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ SaveBusEQ(p_busIndex, p_filePath) {
+ return VBVMR.SetParameterFloat("Command", "SaveBUSEQ[" p_busIndex - 1 "]", p_filePath) == 0
+ }
+ /**
+ * Loads a bus's EQ settings from a file
+ *
+ * @param {Number} p_busIndex - The one-based index of the bus to load
+ * @param {String} p_filePath - The path to load the EQ settings from
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ LoadBusEQ(p_busIndex, p_filePath) {
+ return VBVMR.SetParameterFloat("Command", "LoadBUSEQ[" p_busIndex - 1 "]", p_filePath) == 0
+ }
+ /**
+ * Saves a strip's EQ settings to a file
+ *
+ * @param {Number} p_stripIndex - The one-based index of the strip to save
+ * @param {String} p_filePath - The path to save the EQ settings to
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ SaveStripEQ(p_stripIndex, p_filePath) {
+ return VBVMR.SetParameterFloat("Command", "SaveStripEQ[" p_stripIndex - 1 "]", p_filePath) == 0
+ }
+ /**
+ * Loads a strip's EQ settings from a file
+ *
+ * @param {Number} p_stripIndex - The one-based index of the strip to load
+ * @param {String} p_filePath - The path to load the EQ settings from
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ LoadStripEQ(p_stripIndex, p_filePath) {
+ return VBVMR.SetParameterFloat("Command", "LoadStripEQ[" p_stripIndex - 1 "]", p_filePath) == 0
+ }
+ /**
+ * Recalls a Preset Scene
+ *
+ * @param {String | Number} p_preset - The name of the preset to recall or its one-based index
+ * __________
+ * @returns {Boolean} - true if the command was successful
+ */
+ RecallPreset(p_preset) {
+ if (IsNumber(p_preset))
+ return VBVMR.SetParameterFloat("Command", "Preset[" p_preset - 1 "].Recall", 1) == 0
+ else
+ return VBVMR.SetParameterString("Command", "RecallPreset", p_preset) == 0
+ }
+}
+class VMRControllerBase {
+ __New(p_id, p_stringParamChecker) {
+ this.DefineProp("Id", { Get: (*) => p_id })
+ this.DefineProp("StringParamChecker", { Call: p_stringParamChecker })
+ }
+ /**
+ * @private - Internal method
+ * @description Implements a default property getter, this is invoked when using the object access syntax.
+ *
+ * @param {String} p_key - The name of the parameter.
+ * @param {Array} p_params - An extra param passed when using bracket syntax with a normal prop access (`bus.device["sr"]`).
+ * __________
+ * @returns {Any} The value of the parameter.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ __Get(p_key, p_params) {
+ if (p_params.Length > 0) {
+ for param in p_params {
+ p_key .= IsNumber(param) ? "[" param "]" : "." param
}
- else {
- ; set it to a new value
- return VBVMR.SetParameterFloat("Option", "delay[" . busNum . "]", Min(Max(p_delay,0),500))
+ }
+ return this.GetParameter(p_key)
+ }
+ /**
+ * @private - Internal method
+ * @description Implements a default property setter, this is invoked when using the object access syntax.
+ *
+ * @param {String} p_key - The name of the parameter.
+ * @param {Array} p_params - An extra param passed when using bracket syntax with a normal prop access. `bus.device["wdm"] := "Headset"`
+ * @param {Any} p_value - The value of the parameter.
+ * __________
+ * @returns {Boolean} - `true` if the parameter was set successfully.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ __Set(p_key, p_params, p_value) {
+ if (p_params.Length > 0) {
+ for param in p_params {
+ p_key .= IsNumber(param) ? "[" param "]" : "." param
}
}
+ return this.SetParameter(p_key, p_value) == 0
+ }
+ /**
+ * Implements a default indexer.
+ * this is invoked when using the bracket access syntax.
+ *
+ * @param {String} p_key - The name of the parameter.
+ * __________
+ * @type {Any} - The value of the parameter.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ __Item[p_key] {
+ get => this.GetParameter(p_key)
+ set => this.SetParameter(p_key, Value)
+ }
+ /**
+ * Sets the value of a parameter.
+ *
+ * @param {String} p_name - The name of the parameter.
+ * @param {Any} p_value - The value of the parameter.
+ * __________
+ * @returns {VMRAsyncOp} - An async operation that resolves to `true` if the parameter was set successfully.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ SetParameter(p_name, p_value) {
+ local vmrFunc := this.StringParamChecker(p_name)
+ ? VBVMR.SetParameterString.Bind(VBVMR)
+ : VBVMR.SetParameterFloat.Bind(VBVMR)
+ local result := vmrFunc.Call(this.Id, p_name, p_value)
+ return VMRAsyncOp(() => result == 0, 50)
+ }
+ /**
+ * Returns the value of a parameter.
+ *
+ * @param {String} p_name - The name of the parameter.
+ * __________
+ * @returns {Any} - The value of the parameter.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ GetParameter(p_name) {
+ local vmrFunc := this.StringParamChecker(p_name) ? VBVMR.GetParameterString.Bind(VBVMR) : VBVMR.GetParameterFloat.Bind(VBVMR)
+ return vmrFunc.Call(this.Id, p_name) == 0
+ }
+}
+class VMRMacroButton {
+ static EXECUTABLE := "VoicemeeterMacroButtons.exe"
+ /**
+ * Run the Voicemeeter Macro Buttons application.
+ *
+ * @returns {void}
+ */
+ Run() => Run(VBVMR.DLL_PATH "\" VMRMacroButton.EXECUTABLE, VBVMR.DLL_PATH)
+ /**
+ * Shows/Hides the Voicemeeter Macro Buttons application.
+ * @param {Boolean} p_show - Whether to show or hide the application
+ */
+ Show(p_show := true) {
+ if (p_show) {
+ if (!WinExist("ahk_exe " VMRMacroButton.EXECUTABLE))
+ this.Run(), Sleep(500)
+ WinShow("ahk_exe " VMRMacroButton.EXECUTABLE)
+ }
+ else {
+ WinHide("ahk_exe " VMRMacroButton.EXECUTABLE)
+ }
+ }
+ /**
+ * Sets the status of a given button.
+ * @param {Number} p_index - The one-based index of the button
+ * @param {Number} p_value - The value to set
+ * - `0`: Off
+ * - `1`: On
+ * @param {Number} p_bitMode - The type of the returned value
+ * - `0`: button-state
+ * - `2`: displayed-state
+ * - `3`: trigger-state
+ * __________
+ * @returns {Number} - The status of the button
+ * - `0`: Off
+ * - `1`: On
+ * @throws {VMRError} - If an internal error occurs
+ */
+ SetStatus(p_index, p_value, p_bitMode := 0) => VBVMR.MacroButton_SetStatus(p_index - 1, p_value, p_bitMode)
+ /**
+ * Gets the status of a given button.
+ * @param {Number} p_index - The one-based index of the button
+ * @param {Number} p_bitMode - The type of the returned value
+ * - `0`: button-state
+ * - `2`: displayed-state
+ * - `3`: trigger-state
+ * __________
+ * @returns {Number} - The status of the button
+ * - `0`: Off
+ * - `1`: On
+ * @throws {VMRError} - If an internal error occurs
+ */
+ GetStatus(p_index, p_bitMode := 0) => VBVMR.MacroButton_GetStatus(p_index - 1, p_bitMode)
+}
+class VMRRecorder extends VMRControllerBase {
+ static _stringParameters := ["load"]
+ __New(p_type) {
+ super.__New("recorder", (_, p) => VMRUtils.IndexOf(VMRRecorder._stringParameters, p) != -1)
+ this.DefineProp("TypeInfo", { Get: (*) => p_type })
+ }
+ /**
+ * Arms the specified bus for recording, switching the recording mode to `1` (bus).
+ * Or returns the state of the specified bus (whether it's armed or not).
+ * @param {number} p_index - The bus's one-based index.
+ * __________
+ * @type {boolean} - Whether the bus is armed or not.
+ *
+ * @example
Arm the first bus for recording.
+ * VMR.Recorder.ArmBus[1] := true
+ */
+ ArmBus[p_index] {
+ get {
+ return this.GetParameter("ArmBus(" (p_index - 1) ")")
+ }
+ set {
+ this.SetParameter("mode.recbus", true)
+ this.SetParameter("ArmBus(" . (p_index - 1) . ")", Value)
+ }
+ }
+ /**
+ * Arms the specified strip for recording, switching the recording mode to `0` (strip).
+ * Or returns the state of the specified strip (whether it's armed or not).
+ * @param {number} p_index - The strip's one-based index.
+ * __________
+ * @type {boolean} - Whether the strip is armed or not.
+ *
+ * @example
Arm a strip for recording.
+ * VMR.Recorder.ArmStrip[4] := true
+ */
+ ArmStrip[p_index] {
+ get {
+ return this.GetParameter("ArmStrip(" (p_index - 1) ")")
+ }
+ set {
+ this.SetParameter("mode.recbus", false)
+ this.SetParameter("ArmStrip(" . (p_index - 1) . ")", Value)
+ }
+ }
+ /**
+ * Arms the specified strips for recording, switching the recording mode to `0` (strip) and disarming any armed strips.
+ * @param {Array} p_strips - The strips' one-based indices.
+ *
+ * @example
Arm strips 1, 2, and 4 for recording.
+ * VMR.Recorder.ArmStrips(1, 2, 4)
+ */
+ ArmStrips(p_strips*) {
+ loop this.TypeInfo.StripCount
+ this.ArmStrip[A_Index] := false
+ for i in p_strips
+ this.ArmStrip[i] := true
+ }
+ /**
+ * Loads the specified file into the recorder.
+ * @param {String} p_path - The file's path.
+ * __________
+ * @returns {VMRAsyncOp} - An async operation that resolves to `true` if the parameter was set successfully.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ Load(p_path) => this.SetParameter("load", p_path)
+}
+class VMRVBAN extends VMRControllerBase {
+ /**
+ * Controls a VBAN input stream
+ * @type {VMRControllerBase}
+ */
+ Instream[p_index] {
+ get {
+ return this._instreams.Get(p_index)
+ }
+ }
+ /**
+ * Controls a VBAN output stream
+ * @type {VMRControllerBase}
+ */
+ Outstream[p_index] {
+ get {
+ return this._outstreams.Get(p_index)
+ }
+ }
+ __New(p_type) {
+ super.__New("vban", (*) => false)
+ this.DefineProp("TypeInfo", { Get: (*) => p_type })
+ local stringParams := ["name", "ip"]
+ local streamStringParamChecker := (_, p) => VMRUtils.IndexOf(stringParams, p) != -1
+ local instreams := Array(), outstreams := Array()
+ loop p_type.VbanCount {
+ instreams.Push(VMRControllerBase("vban.instream[" A_Index - 1 "]", streamStringParamChecker))
+ outstreams.Push(VMRControllerBase("vban.outstream[" A_Index - 1 "]", streamStringParamChecker))
+ }
+ this.DefineProp("_instreams", { Get: (*) => instreams })
+ this.DefineProp("_outstreams", { Get: (*) => outstreams })
}
-
-
- class RecorderBase {
- __Set(p_name,p_value){
- local type:= "Float"
- if p_name contains load
- type:= "String"
- return (VBVMR)["SetParameter" type]("Recorder", p_name, p_value)
- }
-
- __Get(p_name){
- local type:= "Float"
- if p_name contains load
- type:= "String"
- return (VBVMR)["GetParameter" type]("Recorder", p_name)
- }
-
- ArmBus(bus, set:=-1){
- if(set > -1){
- VBVMR.SetParameterFloat("Recorder","mode.recbus", 1)
- VBVMR.SetParameterFloat("Recorder","ArmBus(" (bus-1) ")", set)
- }else{
- return VBVMR.GetParameterFloat("Recorder","ArmBus(" (bus-1) ")")
+}
+/**
+ * A wrapper class for Voicemeeter Remote that hides the low-level API to simplify usage.
+ * Must be initialized by calling {@link @VMR.Login|`Login()`} after creating the VMR instance.
+ */
+class VMR {
+ /**
+ * The type of Voicemeeter that is currently running.
+ * @type {VMR.Types} - An object containing information about the current Voicemeeter type.
+ * @see {@link VMR.Types|`VMR.Types`} for a list of available types.
+ */
+ Type := ""
+ /**
+ * The version of Voicemeeter that is currently running.
+ * @type {String} - The version string in the format `v1.v2.v3.v4` (ex: `2.1.0.5`).
+ * @see The AHK function {@link VerCompare|`VerCompare`} can be used to compare version strings.
+ */
+ Version := ""
+ /**
+ * An array of voicemeeter buses
+ * @type {Array} - An array of {@link VMRBus|`VMRBus`} objects.
+ */
+ Bus := Array()
+ /**
+ * An array of voicemeeter strips
+ * @type {Array} - An array of {@link VMRStrip|`VMRStrip`} objects.
+ */
+ Strip := Array()
+ /**
+ * Commands that control various aspects of Voicemeeter
+ * @type {VMRCommands}
+ * @see {@link VMRCommands|`VMRCommands`} for a list of available commands.
+ */
+ Command := VMRCommands()
+ /**
+ * Controls Voicemeeter Potato's FX settings
+ * #### This property is only available when running Voicemeeter Potato (`VMR.Type.Id == 3`).
+ * @type {VMRControllerBase}
+ */
+ Fx := ""
+ /**
+ * Controls Voicemeeter's Patch parameters
+ * @type {VMRControllerBase}
+ */
+ Patch := VMRControllerBase("Patch", (*) => false)
+ /**
+ * Controls Voicemeeter's System Settings
+ * @type {VMRControllerBase}
+ */
+ Option := VMRControllerBase("Option", (*) => false)
+ /**
+ * Controls Voicemeeter's Macro Buttons app
+ * @type {VMRMacroButton}
+ */
+ MacroButton := VMRMacroButton()
+ /**
+ * Controls Voicemeeter's Recorder
+ * #### This property is only available when running Voicemeeter Banana or Potato (`VMR.Type.Id == 2 || VMR.Type.Id == 3`).
+ * @type {VMRRecorder}
+ */
+ Recorder := ""
+ /**
+ * Controls Voicemeeter's VBAN interface
+ * @type {VMRVBAN}
+ */
+ VBAN := ""
+ /**
+ * Creates a new VMR instance and initializes the {@link VBVMR|`VBVMR`} class.
+ * @param {String} p_path - (Optional) The path to the Voicemeeter Remote DLL. If not specified, VBVMR will attempt to find it in the registry.
+ * __________
+ * @throws {VMRError} - If the DLL is not found in the specified path or if voicemeeter is not installed.
+ */
+ __New(p_path := "") {
+ VBVMR.Init(p_path)
+ this._eventListeners := Map()
+ this._eventListeners.CaseSense := "Off"
+ for (event in VMRConsts.Events.OwnProps())
+ this._eventListeners[event] := []
+ }
+ /**
+ * Initializes the VMR instance and opens the communication pipe with Voicemeeter.
+ * @param {Boolean} p_launchVoicemeeter - (Optional) Whether to launch Voicemeeter if it's not already running. Defaults to `true`.
+ * __________
+ * @returns {VMR} The {@link VMR|`VMR`} instance.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ Login(p_launchVoicemeeter := true) {
+ local loginStatus := VBVMR.Login()
+ ; Check if we should launch the Voicemeeter UI
+ if (loginStatus != 0 && p_launchVoicemeeter) {
+ local vmPID := this.RunVoicemeeter()
+ WinWait("ahk_class VBCABLE0Voicemeeter0MainWindow0 ahk_pid" vmPID)
+ Sleep(2000)
+ }
+ this.Version := VBVMR.GetVoicemeeterVersion()
+ this.Type := VMR.Types.GetType(VBVMR.GetVoicemeeterType())
+ if (!this.Type)
+ throw VMRError("Unsupported Voicemeeter type: " . VBVMR.GetVoicemeeterType(), this.Login.Name, p_launchVoicemeeter)
+ OnExit(this.__Delete.Bind(this))
+ ; Initialize VMR components (bus/strip arrays, macro buttons, etc)
+ this._InitializeComponents()
+ ; Setup timers
+ this._syncTimer := this.Sync.Bind(this)
+ this._levelsTimer := this._UpdateLevels.Bind(this)
+ SetTimer(this._syncTimer, VMRConsts.SYNC_TIMER_INTERVAL)
+ SetTimer(this._levelsTimer, VMRConsts.LEVELS_TIMER_INTERVAL)
+ ; Listen for device changes to update the device arrays
+ this._updateDevicesCallback := this.UpdateDevices.Bind(this)
+ OnMessage(VMRConsts.WM_DEVICE_CHANGE, this._updateDevicesCallback)
+ this.Sync()
+ return this
+ }
+ /**
+ * Attempts to run Voicemeeter.
+ * When passing a `p_type`, it will only attempt to run the specified Voicemeeter type,
+ * otherwise it will attempt to run every voicemeeter type descendingly until one is successfully launched.
+ *
+ * @param {Number} p_type - (Optional) The type of Voicemeeter to run.
+ * __________
+ * @returns {Number} The PID of the launched Voicemeeter process.
+ * @throws {VMRError} If the specified Voicemeeter type is invalid, or if no Voicemeeter type could be launched.
+ */
+ RunVoicemeeter(p_type?) {
+ local vmPID := ""
+ if (IsSet(p_type)) {
+ local vmInfo := VMR.Types.GetType(p_type)
+ if (!vmInfo)
+ throw VMRError("Invalid Voicemeeter type: " . p_type, this.RunVoicemeeter.Name, p_type)
+ local vmPath := VBVMR.DLL_PATH . "\" . vmInfo.Executable
+ Run(vmPath, VBVMR.DLL_PATH, "Hide", &vmPID)
+ return vmPID
+ }
+ local vmTypeCount := VMR.Types.Count
+ loop vmTypeCount {
+ try {
+ vmPID := this.RunVoicemeeter((vmTypeCount + 1) - A_Index)
+ return vmPID
}
}
-
- ArmStrips(strip*){
- loop {
- Try
- this.armStrip(A_Index,0)
- Catch
- Break
+ throw VMRError("Failed to launch Voicemeeter", this.RunVoicemeeter.Name)
+ }
+ /**
+ * Registers a callback function to be called when the specified event is fired.
+ * @param {String} p_event - The name of the event to listen for.
+ * @param {Func} p_listener - The function to call when the event is fired.
+ * __________
+ * @example vm.On(VMRConsts.Events.ParametersChanged, () => MsgBox("Parameters changed!"))
+ * @see {@link VMRConsts.Events|`VMRConsts.Events`} for a list of available events.
+ * __________
+ * @throws {VMRError} If the specified event is invalid, or if the listener is not a valid `Func` object.
+ */
+ On(p_event, p_listener) {
+ if (!this._eventListeners.Has(p_event))
+ throw VMRError("Invalid event: " p_event, this.On.Name, p_event, p_listener)
+ if !(p_listener is Func)
+ throw VMRError("Invalid listener: " String(p_listener), this.On.Name, p_event, p_listener)
+ local eventListeners := this._eventListeners[p_event]
+ if (VMRUtils.IndexOf(eventListeners, p_listener) == -1)
+ eventListeners.Push(p_listener)
+ }
+ /**
+ * Removes a callback function from the specified event.
+ * @param {String} p_event - The name of the event.
+ * @param {Func} p_listener - (Optional) The function to remove, if omitted, all listeners for the specified event will be removed.
+ * __________
+ * @example vm.Off("parametersChanged", myListener)
+ * @see {@link VMRConsts.Events|`VMRConsts.Events`} for a list of available events.
+ * __________
+ * @returns {Boolean} Whether the listener was removed.
+ * @throws {VMRError} If the specified event is invalid, or if the listener is not a valid `Func` object.
+ */
+ Off(p_event, p_listener?) {
+ if (!this._eventListeners.Has(p_event))
+ throw VMRError("Invalid event: " p_event, this.Off.Name, p_event, p_listener)
+ if (!IsSet(p_listener)) {
+ this._eventListeners[p_event] := Array()
+ return true
+ }
+ if !(p_listener is Func)
+ throw VMRError("Invalid listener: " String(p_listener), this.Off.Name, p_event, p_listener)
+ local eventListeners := this._eventListeners[p_event]
+ local listenerIndex := VMRUtils.IndexOf(eventListeners, p_listener)
+ if (listenerIndex == -1)
+ return false
+ eventListeners.RemoveAt(listenerIndex)
+ return true
+ }
+ /**
+ * Synchronizes the VMR instance with Voicemeeter.
+ * __________
+ * @returns {Boolean} - Whether voicemeeter state has changed since the last sync.
+ */
+ Sync() {
+ static ignoreMsg := false
+ try {
+ ; Prevent multiple syncs from running at the same time
+ Critical("On")
+ local dirtyParameters := VBVMR.IsParametersDirty()
+ , dirtyMacroButtons := VBVMR.MacroButton_IsDirty()
+ ; Api calls were successful -> reset ignoreMsg flag
+ ignoreMsg := false
+ if (dirtyParameters > 0)
+ this._DispatchEvent(VMRConsts.Events.ParametersChanged)
+ if (dirtyMacroButtons > 0)
+ this._DispatchEvent(VMRConsts.Events.MacroButtonsChanged)
+ ; Check if there are any listeners for midi messages
+ local midiListeners := this._eventListeners[VMRConsts.Events.MidiMessage]
+ if (midiListeners.Length > 0) {
+ ; Get new midi messages and dispatch event if there's any
+ local midiMessages := VBVMR.GetMidiMessage()
+ if (midiMessages && midiMessages.Length > 0)
+ this._DispatchEvent(VMRConsts.Events.MidiMessage, midiMessages)
}
- for i in strip
- Try this.armStrip(strip[i],1)
- }
-
- ArmStrip(strip, set:=-1){
- if(set > -1){
- VBVMR.SetParameterFloat("Recorder","mode.recbus", 0)
- VBVMR.SetParameterFloat("Recorder","ArmStrip(" . (strip-1) . ")", set)
- }else{
- return VBVMR.GetParameterFloat("Recorder","ArmStrip(" (strip-1) ")")
+ Critical("Off")
+ return dirtyParameters || dirtyMacroButtons
+ }
+ catch Error as err {
+ Critical("Off")
+ if (ignoreMsg)
+ return false
+ result := MsgBox(
+ Format("An error occurred during VMR sync:`n{}`nDetails: {}`nAttempt to restart Voicemeeter?", err.Message, err.Extra),
+ "VMR",
+ "YesNo Icon! T10"
+ )
+ switch (result) {
+ case "Yes":
+ this.runVoicemeeter(this.Type.id)
+ case "No", "Timeout":
+ ignoreMsg := true
}
+ Sleep(1000)
+ return false
}
}
-
-}
-
-class VBVMR {
- static DLL
- , DLL_PATH
- , DLL_FILE
- , VM_TYPE
- , BUSCOUNT
- , STRIPCOUNT
- , STR_TYPE
- , VBANINCOUNT
- , VBANOUTCOUNT
- , FUNC_ADDR:={ Login:0
- , Logout:0
- , SetParameterFloat:0
- , SetParameterStringW:0
- , SetParameterStringA:0
- , GetParameterFloat:0
- , GetParameterStringW:0
- , GetParameterStringA:0
- , GetVoicemeeterType:0
- , GetLevel:0
- , Output_GetDeviceNumber:0
- , Output_GetDeviceDescW:0
- , Output_GetDeviceDescA:0
- , Input_GetDeviceNumber:0
- , Input_GetDeviceDescW:0
- , Input_GetDeviceDescA:0
- , IsParametersDirty:0
- , MacroButton_IsDirty:0
- , MacroButton_GetStatus:0
- , MacroButton_SetStatus:0
- , GetMidiMessage:0
- , SetParameters:0
- , SetParametersW:0}
-
- Login(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.Login)
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Login returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- return errLevel
- }
-
- Logout(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.Logout)
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Logout returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- return errLevel
- }
-
- SetParameterFloat(p_prefix, p_parameter, p_value){
- errLevel := DllCall(VBVMR.FUNC_ADDR.SetParameterFloat, "AStr" , p_prefix . "." . p_parameter , "Float" , p_value, "Int")
- if (errLevel<0)
- Throw, Exception(Format("`nVBVMR_SetParameterFloat returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- return p_value
+ /**
+ * Executes a Voicemeeter script (**not** an AutoHotkey script).
+ * - Scripts can contain one or more parameter changes
+ * - Changes can be seperated by a new line, `;` or `,`.
+ * - Indices in the script are zero-based.
+ *
+ * @param {String} p_script - The script to execute.
+ * __________
+ * @throws {VMRError} If an error occurs while executing the script.
+ */
+ Exec(p_script) {
+ local result := VBVMR.SetParameters(p_script)
+ if (result > 0)
+ throw VMRError("An error occurred while executing the script at line: " . result, this.Exec.Name, p_script)
}
-
- SetParameterString(p_prefix, p_parameter, p_value){
- errLevel := DllCall(VBVMR.FUNC_ADDR["SetParameterString" . VBVMR.STR_TYPE], "AStr", p_prefix . "." . p_parameter , VBVMR.STR_TYPE . "Str" , p_value , "Int")
- if (errLevel<0)
- Throw, Exception(Format("`nVBVMR_SetParameterString returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- return p_value
+ /**
+ * Updates the list of strip/bus devices.
+ * @param {Number} p_wParam - (Optional) If passed, must be equal to {@link VMRConsts.WM_DEVICE_CHANGE_PARAM|`VMRConsts.WM_DEVICE_CHANGE_PARAM`} to update the device arrays.
+ * __________
+ * @throws {VMRError} If an internal error occurs.
+ */
+ UpdateDevices(p_wParam?, *) {
+ if (IsSet(p_wParam) && p_wParam != VMRConsts.WM_DEVICE_CHANGE_PARAM)
+ return
+ VMRStrip.Devices := Array()
+ loop VBVMR.Input_GetDeviceNumber()
+ VMRStrip.Devices.Push(VBVMR.Input_GetDeviceDesc(A_Index - 1))
+ VMRBus.Devices := Array()
+ loop VBVMR.Output_GetDeviceNumber()
+ VMRBus.Devices.Push(VBVMR.Output_GetDeviceDesc(A_Index - 1))
+ SetTimer(() => this._DispatchEvent(VMRConsts.Events.DevicesUpdated), -20)
}
-
- GetParameterFloat(p_prefix, p_parameter){
- local value
- VarSetCapacity(value, 4)
- errLevel := DllCall(VBVMR.FUNC_ADDR.GetParameterFloat, "AStr" , p_prefix . "." . p_parameter , "Ptr" , &value, "Int")
- if (errLevel<0)
- Throw, Exception(Format("`nVBVMR_GetParameterFloat returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- value := NumGet(&value, 0, "Float")
+ ToString() {
+ local value := "VMR:`n"
+ if (this.Type) {
+ value .= "Logged into " . this.Type.name . " in (" . VBVMR.DLL_PATH . ")"
+ }
+ else {
+ value .= "Not logged in"
+ }
return value
}
-
- GetParameterString(p_prefix, p_parameter){
- local value
- VarSetCapacity(value, A_IsUnicode? 1024 : 512)
- errLevel := DllCall(VBVMR.FUNC_ADDR["GetParameterString" . VBVMR.STR_TYPE], "AStr" , p_prefix . "." . p_parameter , "Ptr" , &value , "Int")
- if (errLevel<0)
- Throw, Exception(Format("`nVBVMR_GetParameterString returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- value := StrGet(&value,512)
- return value
+ /**
+ * @private - Internal method
+ * @description Dispatches an event to all listeners.
+ *
+ * @param {String} p_event - The name of the event to dispatch.
+ * @param {Array} p_args - (Optional) An array of arguments to pass to the listeners.
+ */
+ _DispatchEvent(p_event, p_args*) {
+ local eventListeners := this._eventListeners[p_event]
+ if (eventListeners.Length == 0)
+ return
+ _eventDispatcher() {
+ for (listener in eventListeners) {
+ if (p_args.Length == 0 || listener.MaxParams < p_args.Length)
+ listener()
+ else
+ listener(p_args*)
+ }
+ }
+ SetTimer(_eventDispatcher, -1)
}
-
- GetLevel(p_type, p_channel){
- local level
- VarSetCapacity(level,4)
- errLevel := DllCall(VBVMR.FUNC_ADDR.GetLevel, "Int", p_type, "Int", p_channel, "Ptr", &level)
- if(errLevel<0){
- SetTimer,, Off
- Throw, Exception(Format("`nVBVMR_GetLevel returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- }
- level := NumGet(&level, 0, "Float")
- return level
- }
-
- GetVoicemeeterType(){
- local vtype
- VarSetCapacity(vtype, 4)
- errLevel := DllCall(VBVMR.FUNC_ADDR.GetVoicemeeterType, "Ptr", &vtype, "Int")
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_GetVoicemeeterType returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- vtype:= NumGet(&vtype, 0, "Int")
- return vtype
- }
-
- Output_GetDeviceNumber(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.Output_GetDeviceNumber,"Int")
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Output_GetDeviceNumber returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- else
- return errLevel
- }
-
- Output_GetDeviceDesc(p_index){
- local name, driver, device := {}
- VarSetCapacity(name, A_IsUnicode? 1024 : 512)
- VarSetCapacity(driver, 4)
- errLevel := DllCall(VBVMR.FUNC_ADDR["Output_GetDeviceDesc" . VBVMR.STR_TYPE], "Int", p_index, "Ptr" , &driver , "Ptr", &name, "Ptr", 0, "Int")
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Output_GetDeviceDesc returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- driver := NumGet(&driver, 0, "UInt")
- name := StrGet(&name,512)
- device.name := name
- device.driver := (driver=3 ? "wdm" : (driver=4 ? "ks" : (driver=5 ? "asio" : "mme")))
- return device
- }
-
- Input_GetDeviceNumber(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.Input_GetDeviceNumber,"Int")
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Input_GetDeviceNumber returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- else
- return errLevel
- }
-
- Input_GetDeviceDesc(p_index){
- local name, driver, device := {}
- VarSetCapacity(name, A_IsUnicode? 1024 : 512)
- VarSetCapacity(driver, 4)
- errLevel := DllCall(VBVMR.FUNC_ADDR["Input_GetDeviceDesc" . VBVMR.STR_TYPE], "Int", p_index, "Ptr" , &driver , "Ptr", &name, "Ptr", 0, "Int")
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_Input_GetDeviceDesc returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- driver := NumGet(&driver, 0, "UInt")
- name := StrGet(&name,512)
- device.name := name
- device.driver := (driver=3 ? "wdm" : (driver=4 ? "ks" : (driver=5 ? "asio" : "mme")))
- return device
- }
-
- IsParametersDirty(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.IsParametersDirty)
- if(errLevel<0)
- Throw, Exception(Format("`nVBVMR_IsParametersDirty returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- else
- return errLevel
- }
-
- MacroButton_GetStatus(nuLogicalButton, bitMode){
- local pValue
- VarSetCapacity(pValue, 4)
- errLevel := DllCall(VBVMR.FUNC_ADDR.MacroButton_GetStatus, "Int" , nuLogicalButton , "Ptr", &pValue, "Int", bitMode, "Int")
- if (errLevel<0)
- Throw, Exception("VBVMR_MacroButton_GetStatus returned " . errLevel . "`n DllCall returned " . ErrorLevel, -1)
- pValue := NumGet(&pValue, 0, "Float")
- return pValue
- }
-
- MacroButton_SetStatus(nuLogicalButton, fValue, bitMode){
- errLevel := DllCall(VBVMR.FUNC_ADDR.MacroButton_SetStatus, "Int" , nuLogicalButton , "Float" , fValue, "Int", bitMode, "Int")
- if (errLevel<0)
- Throw, Exception("VBVMR_MacroButton_SetStatus returned " . errLevel, -1)
- return fValue
- }
-
- MacroButton_IsDirty(){
- errLevel := DllCall(VBVMR.FUNC_ADDR.MacroButton_IsDirty)
- if(errLevel<0)
- Throw, Exception("VBVMR_MacroButton_IsParametersDirty returned " . errLevel, -1)
- else
- return errLevel
- }
-
- GetMidiMessage(){
- local nBytes:= 1024, dBuffer:="", tempArr:= Array()
- VarSetCapacity(dBuffer, nBytes)
- errLevel := DllCall(VBVMR.FUNC_ADDR.GetMidiMessage, "Ptr", &dBuffer, "Int", nBytes)
- if errLevel between -1 and -2
- Throw, Exception("VBVMR_GetMidiMessage returned " . errLevel, -1)
- loop %errLevel% {
- tempArr[A_Index]:= Format("0x{:X}",NumGet(&dBuffer, A_Index - 1, "UChar"))
- }
- return tempArr.Length()? tempArr : ""
- }
-
- SetParameters(script){
- errLevel := DllCall(VBVMR.FUNC_ADDR["SetParameters" . VBVMR.STR_TYPE], VBVMR.STR_TYPE . "Str" , script , "Int")
- if (errLevel<0)
- Throw, Exception(Format("`nVBVMR_SetParameters returned {}`nDllCall returned {}", errLevel, ErrorLevel))
- return errLevel
- }
-
- __getAddresses(){
- for fName in VBVMR.FUNC_ADDR
- (VBVMR.FUNC_ADDR)[fName]:= DllCall("GetProcAddress", "Ptr", VBVMR.DLL, "AStr", "VBVMR_" . fName, "Ptr")
- }
-}
\ No newline at end of file
+ /**
+ * @private - Internal method
+ * @description Initializes VMR's components (bus/strip arrays, macroButton obj, etc).
+ */
+ _InitializeComponents() {
+ this.Bus := Array()
+ loop this.Type.busCount {
+ this.Bus.Push(VMRBus(A_Index - 1, this.Type.id))
+ }
+ this.Strip := Array()
+ loop this.Type.stripCount {
+ this.Strip.Push(VMRStrip(A_Index - 1, this.Type.id))
+ }
+ if (this.Type.Id > 1)
+ this.Recorder := VMRRecorder(this.Type)
+ if (this.Type.Id == 3)
+ this.Fx := VMRControllerBase("Fx", (*) => false)
+ this.VBAN := VMRVBAN(this.Type)
+ this.UpdateDevices()
+ VMRAudioIO.IS_CLASS_INIT := true
+ }
+ /**
+ * @private - Internal method
+ * @description Updates the levels of all buses/strips.
+ */
+ _UpdateLevels() {
+ local bus, strip
+ for (bus in this.Bus) {
+ bus._UpdateLevels()
+ }
+ for (strip in this.Strip) {
+ strip._UpdateLevels()
+ }
+ this._DispatchEvent(VMRConsts.Events.LevelsUpdated)
+ }
+ /**
+ * @private - Internal method
+ * @description Prepares the VMR instance for deletion (turns off timers, etc..) and logs out of Voicemeeter.
+ */
+ __Delete(*) {
+ if (!this.Type)
+ return
+ this.Type := ""
+ this.Bus := ""
+ this.Strip := ""
+ if (this._syncTimer)
+ SetTimer(this._syncTimer, 0)
+ if (this._levelsTimer)
+ SetTimer(this._levelsTimer, 0)
+ if (this._updateDevicesCallback)
+ OnMessage(VMRConsts.WM_DEVICE_CHANGE, this._updateDevicesCallback, 0)
+ while (this.Sync()) {
+ }
+ ; Make sure all commands finish executing before logging out
+ Sleep(100)
+ VBVMR.Logout()
+ }
+ /**
+ * Known Voicemeeter types info
+ */
+ class Types {
+ static Count := 3
+ static Standard := VMR.Types(1, "Voicemeeter", "voicemeeter.exe", 2, 3, 4)
+ static Banana := VMR.Types(2, "Voicemeeter Banana", "voicemeeterpro.exe", 5, 5, 8)
+ static Potato := VMR.Types(3, "Voicemeeter Potato", "voicemeeter8" (A_Is64bitOS ? "x64" : "") ".exe", 8, 8, 8)
+ __New(id, name, executable, busCount, stripCount, vbanCount) {
+ this.Id := id
+ this.Name := name
+ this.Executable := executable
+ this.BusCount := busCount
+ this.StripCount := stripCount
+ this.VbanCount := vbanCount
+ }
+ /**
+ * Returns the voicemeeter type with the specified id.
+ * @param {Number} p_id - The id of the type.
+ * @returns {VMR.Types}
+ */
+ static GetType(p_id) {
+ switch (p_id) {
+ case 1:
+ return VMR.Types.Standard
+ case 2:
+ return VMR.Types.Banana
+ case 3:
+ return VMR.Types.Potato
+ }
+ }
+ }
+}
diff --git a/docs/README.md b/docs/README.md
index 8321584..92088fc 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,30 +1,41 @@
-## Getting started
-To use `VMR.ahk` in your script, follow these steps:
-1. Download the latest pre-built version from the [`dist` folder](https://raw.githubusercontent.com/SaifAqqad/VMR.ahk/master/dist/VMR.ahk) or follow the build instructions below
-
-2. Include it using `#Include VMR.ahk` or copy it to a [library folder](https://www.autohotkey.com/docs/Functions.htm#lib) and use `#Include `
-
-3. create an instance of the VMR class and log in to the API:
- ```autohotkey
- voicemeeter := new VMR().login()
- ```
- you can optionally pass the path to voicemeeter's folder:
- ```autohotkey
- voicemeeter := new VMR("C:\path\to\voicemeeter\").login()
- ```
-4. The `VMR` object will have two arrays, `bus` and`strip`, as well as other objects, that will allow you to control voicemeeter in AHK.
- ```autohotkey
- voicemeeter.bus[1].mute := true
- voicemeeter.strip[4].gain++
- ```
+## Quick start
+
+To use `VMR.ahk` in your script, follow one of the following methods:
+
+### ahkpm installation
+
+1. Install and set up [ahkpm](https://github.com/joshuacc/ahkpm)
+2. Run `ahkpm install gh:SaifAqqad/VMR.ahk`
+3. Include VMR in your script by running `ahkpm include gh:SaifAqqad/VMR.ahk -f myScript.ahk`
+
+### Manual Installation
+
+1. Download the latest pre-built version from the [`dist` folder](https://raw.githubusercontent.com/SaifAqqad/VMR.ahk/master/dist/VMR.ahk ':target=_blank') / [latest release](https://github.com/SaifAqqad/VMR.ahk/releases ':target=_blank') or follow the build instructions below
+2. Include it using `#Include VMR.ahk` or copy it to a [library folder](https://www.autohotkey.com/docs/v2/Scripts.htm#lib ':target=_blank') and use `#Include `
+
+!> The current version of VMR ***only*** supports AHK v2, the AHK v1 version is still available on the [v1 branch](https://github.com/SaifAqqad/VMR.ahk/tree/v1 ':target=_blank'), see [v1 docs](https://vmr-v1.vercel.app/ ':target=_blank').
+
+## Basic usage
+- Create an instance of the [`VMR`](./classes/vmr) class and log in to the API:
+ ```autohotkey
+ voicemeeter := VMR().Login()
+ ```
+- The [`VMR`](./classes/vmr) instance will have two arrays (`Bus` and `Strip`), as well as other properties/methods that will allow you to control voicemeeter in AHK
+ ```autohotkey
+ voicemeeter.Bus[1].mute := true
+ voicemeeter.Strip[4].gain++
+ ```
## Build instructions
-To build `VMR.ahk` yourself, run the build script:
+
+To build `VMR.ahk`, either run the vscode task `Build VMR` or run the build script using ahkpm or manually:
+
```powershell
-.\Build.ahk
-# Or if you installed autohotkey using scoop:
-# autohotkey.exe .\Build.ahk
+# ahkpm
+ahkpm run build
+# Manually
+Autohotkey.exe ".\Build.ahk" ".\VMR.ahk" "..\dist\VMR.ahk" ""
```
-This documentation is for VMR.ahk, If you need help with the Voicemeeter API check out [their documentation](http://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf)
+This documentation is for VMR.ahk, If you need help with the Voicemeeter API check out [their documentation](http://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf ':target=_blank')
diff --git a/docs/VMR-Class/README.md b/docs/VMR-Class/README.md
deleted file mode 100644
index f15ffff..0000000
--- a/docs/VMR-Class/README.md
+++ /dev/null
@@ -1,65 +0,0 @@
-## `VMR` Class
-
-
-### `__New([p_path])` Constructor
-Initilizes the VBVMR class (the actual wrapper) by setting the DLL path and type (64/32) as well as the string encoding which is based on the type of AHK that's running the script (Unicode/ANSI), then loads the correct DLL and its functions addresses.
-
-*Note: for VMR to work properly, the script needs to be persistent, scripts that have GUIs or hotkeys are implicitly persistent, to make a regular script persistent add `#Persistent` to the top of the script*
-
----
-
-### Properties
-
-* #### `bus` and `strip` Arrays
-Array of [`bus`/`strip` objects](/VMR-Class/bus-strip-object.md 'Bus/Strip object').
-* #### [`recorder`](/VMR-Class/recorder-object.md 'Recorder object')
-Use this object to control voicemeeter's recorder.
-* #### [`vban`](/VMR-Class/vban-object.md 'VBAN object')
-Use this object to control voicemeeter's VBAN interface
-* #### [`command`](/VMR-Class/command-object.md 'Command object')
-Use this object to access command methods.
-* #### [`option`](/VMR-Class/option-object.md 'Option object')
-Use this object to access/modify option parameters.
-* #### [`macroButton`](/VMR-Class/macrobutton-object.md 'MacroButton object')
-Use this object to access/modify macro buttons statuses.
-
----
-
-### Methods
-
-* #### `login()`
-Calls voicemeeter's login function and initilizes VMR class properties (objects and arrays).
-This method needs to be called first, in order to use the VMR class.
-* #### `getType()`
-Returns voicemeeter's type. (1 -> voicemeeter, 2 -> banana, 3 -> potato).
-* #### `runVoicemeeter([type])`
-Runs the highest version installed , or a specific version if `type` is passed.
-* #### `updateDevices()`
-Updates the internal array of input and output devices, that's used for setting bus/strips devices
-* #### `exec(script)`
-Executes a string of voicemeeter commands, see [`script_example.ahk`](https://github.com/SaifAqqad/VMR.ahk/blob/master/examples/script_example.ahk)
-* #### `getBusDevices()`/`getStripDevices()`
-Returns an array of input/output devices, each device is an object with `name` and `driver` properties
-
----
-
-### Callback functions
-Set callback functions for certain events (e.g. to update a user interface)
-
-* #### `onUpdateLevels`
-called whenever the [`level`](/VMR-Class/bus-strip-object?id=level-array) array for bus/strip objects is updated.
-* #### `onUpdateParameters`
-called whenever voicemeeter's parameters change on the UI or by another app.
-* #### `onUpdateMacrobuttons`
-called whenever a macrobutton's state is changed.
-* #### `onMidiMessage`
-called whenever voicemeeter receives a MIDI message
-
-```autohotkey
- ; Set a function object
- voicemeeter.onUpdateLevels := Func("syncLevels")
-```
-
-- See [ui_example.ahk](https://github.com/SaifAqqad/VMR.ahk/blob/master/examples/ui_example.ahk)
-- See [midi_message_example.ahk](https://github.com/SaifAqqad/VMR.ahk/blob/master/examples/midi_message_example.ahk)
-- [More info on function objects (AHK docs)](https://www.autohotkey.com/docs/objects/Func.htm)
diff --git a/docs/VMR-Class/bus-strip-object.md b/docs/VMR-Class/bus-strip-object.md
deleted file mode 100644
index ed6d894..0000000
--- a/docs/VMR-Class/bus-strip-object.md
+++ /dev/null
@@ -1,91 +0,0 @@
-## `bus`/`strip`
-
-Use this object to access or modify bus/strip parameters.
-
-### Properties
-
-* #### `gain_limit`
- The maximum gain (in dB) that can be applied to a bus/strip, Increamenting the gain above this value will reapply the limit
-* #### `level` array
- Contains the current level (in dB) for every channel a bus/strip has, physical (hardware) strips have 2 channels (left, right), Buses and virtual strips have 8 channels
-* #### `name`
- The name of the bus/strip (e.g. `A1`, `B1`, `Virtual Input #1`)
-
----
-### Methods
-
-* #### `setParameter(parameter, value)`
- Sets the value of a bus/strip's parameter
-* #### `getParameter(parameter)`
- Returns the value of a bus/strip's parameter
-* #### `getGainPercentage()`
- Returns the bus/strip's gain as a scalar percentage
-* #### `setGainPercentage(percentage)`
- Sets the bus/strip's gain using a scalar percentage
-* #### `getPercentage(dB)`
- Converts a dB value to a scalar percentage
-* #### `getdB(percentage)`
- Converts a scalar percentage to a dB value
-* #### `isPhysical()`
- Returns `1` if the bus/strip is a physical one (Hardware bus/strip), otherwise return `0`
-
----
-
-### Parameters
-for an up-to-date list of all `bus`/`strip` parameters, check out [VBVMR docs](http://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf#page=11)
-
----
-
-### Examples
-#### Set any parameter
-
-```autohotkey
- voicemeeter.bus[1].gain := 10.5
- voicemeeter.strip[3].FadeTo := "(-10.0, 3000)"
- ; or you can use setParameter()
- voicemeeter.bus[1].setParameter("gain", 10.5)
-```
-
-#### Retrieve any parameter
-```autohotkey
- current_gain := voicemeeter.bus[3].gain
- is_assigned := voicemeeter.strip[1].B2
- is_muted := voicemeeter.bus[1].mute
-```
-
-#### Set/Retrieve the device
-```autohotkey
- ; bus[1].device[driver] := device_name
- ; driver can be ["wdm","mme","asio","ks]
- ; device_name can be any substring of the full name
- voicemeeter.strip[2].device["ks"] := "Microphone (2)"
-
- ; Retrieve the device's name
- device_name := voicemeeter.bus[2].device
-```
-
-#### Increment/decrement gain parameter
-```autohotkey
- voicemeeter.bus[3].gain--
- db := ++voicemeeter.bus[1].gain
- ; db contains the incremented value
-```
-
-#### Toggle mute parameter
-```autohotkey
- voicemeeter.bus[1].mute := 1
-
- voicemeeter.bus[1].mute := -1
- is_muted := voicemeeter.bus[1].mute ; 0
- ; or
- voicemeeter.bus[1].mute--
- is_muted := voicemeeter.bus[1].mute ; 1
-```
-
-#### Retrieve the current peak level of a bus/strip
-
-
-```autohotkey
- peak_level := Max(voicemeeter.bus[1].level*)
-```
-See [level_stabilizer_example.ahk](https://github.com/SaifAqqad/VMR.ahk/blob/master/examples/level_stabilizer_example.ahk)
diff --git a/docs/VMR-Class/command-object.md b/docs/VMR-Class/command-object.md
deleted file mode 100644
index 6386b19..0000000
--- a/docs/VMR-Class/command-object.md
+++ /dev/null
@@ -1,65 +0,0 @@
-## `command`
-
-Use this object to access command methods.
-
-### Methods
-
-* #### `restart()`
-Restarts Voicemeeter's audio engine
-* #### `shutdown()`
-Closes Voicemeeter completely
-* #### `show([state])`
-Shows/Hides Voicemeeter's window
-```autohotkey
- voicemeeter.command.show(false) ; hides the window
-```
-* #### `lock([state])`
-Locks/Unlocks Voicemeeter's window
-```autohotkey
- voicemeeter.command.lock(true) ; locks the window
-```
-* #### `eject()`
-Ejects the recorder's cassette (releases the audio file)
-* #### `reset()`
-Resets All configuration
-* #### `save([filePath])`
-Saves Voicemeeter's configuration to a file
-```autohotkey
- voicemeeter.command.save("VMconfig.xml") ; saves the file in the user's documents folder
-```
-* #### `load([filePath])`
-Loads Voicemeeter's configuration from a file
-```autohotkey
- voicemeeter.command.load("C:\config\voicemeeter.xml")
-```
-* #### `showVBANChat([state])`
-Shows/hides the VBAN-Chat Dialog
-```autohotkey
- voicemeeter.command.showVBANChat(true)
-```
-* #### `state(buttonIndex, state)`
-Changes the actual state of a macro button. `buttonIndex` is zero-based.
-```autohotkey
- voicemeeter.command.state(0,1) ; sets the state of the first macro button to 1
-```
-* #### `stateOnly(buttonIndex, state)`
-Changes the visual state of a macro button.
-```autohotkey
- voicemeeter.command.stateOnly(2,0)
- ; releases the key visually but does not run the code programmed into the macrobutton.
-```
-* #### `trigger(buttonIndex, state)`
-Changes a button's trigger state.
-```autohotkey
- voicemeeter.command.trigger(3,1)
-```
-* #### `saveBusEQ(busIndex, filePath)`
-Saves the bus EQ settings to a file. `busIndex` is zero-based.
-```autohotkey
- voicemeeter.command.saveBusEQ(0,"C:\config\bus0_eq.xml")
-```
-* #### `loadBusEQ(busIndex, filePath)`
-Loads the bus EQ settings from a file.
-```autohotkey
- voicemeeter.command.loadBusEQ(2,"C:\config\bus2_eq.xml")
-```
\ No newline at end of file
diff --git a/docs/VMR-Class/macrobutton-object.md b/docs/VMR-Class/macrobutton-object.md
deleted file mode 100644
index f07f60c..0000000
--- a/docs/VMR-Class/macrobutton-object.md
+++ /dev/null
@@ -1,45 +0,0 @@
-## `macroButton`
-
-Use this object to access/modify macro buttons status.
-
-### Methods
-* #### `show([state])`
- Shows/Hides the MacroButtons app window
- ```autohotkey
- voicemeeter.macroButton.show(1)
-
- voicemeeter.macroButton.show(0)
- ```
-
-* #### `run()`
- Runs the MacroButtons app
- ```autohotkey
- voicemeeter.macroButton.run()
- ```
-
-* #### `setStatus(buttonIndex, newStatus [, bitmode])`
- Set the status of a macro button.
-
- `buttonIndex` is zero-based.
-
- `bitmode` defines what kind of value will be set, it's optional and `0` by default, possible values are:
-
- - `0` : Actual button's state
- - `2` : Displayed (visual) State only
- - `3` : Trigger state
- ```autohotkey
- ; set macrobutton 0 to on.
- voicemeeter.macroButton.setStatus(0,1)
-
- ; sets macro button 2 to have trigger on
- voicemeeter.macroButton.setStatus(2,1,3)
- ```
-
-* #### `getStatus(buttonIndex [, bitmode])`
- Retrieve the status of a macro button
- ```autohotkey
- buttonStatus := voicemeeter.macroButton.getStatus(1)
- ```
-
-
- See [VBVMR docs](http://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf#page=8) for more info
\ No newline at end of file
diff --git a/docs/VMR-Class/option-object.md b/docs/VMR-Class/option-object.md
deleted file mode 100644
index d4e15d4..0000000
--- a/docs/VMR-Class/option-object.md
+++ /dev/null
@@ -1,30 +0,0 @@
-## `option`
-
-Use this object to access/modify option parameters.
-
-### Methods
-
-* #### `delay(busIndex, [delay])`
-Set the bus output delay. If `delay` is not passed, it will return the current delay for that bus
-
----
-
-### Parameters
-for an up-to-date list of all `option` parameters, check out [VBVMR docs](https://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf#page=14)
-
----
-
-### Examples
-#### Set any parameter
-
-```autohotkey
- voicemeeter.option.sr := 44.1
- voicemeeter.option["buffer.wdm"] := 1024
- voicemeeter.option.delay(2,200)
-```
-#### Retrieve any parameter
-```autohotkey
- is_exclusif := voicemeeter.option["mode.exclusif"]
- delay := voicemeeter.option.delay(1)
-```
-
diff --git a/docs/VMR-Class/recorder-object.md b/docs/VMR-Class/recorder-object.md
deleted file mode 100644
index 1df3115..0000000
--- a/docs/VMR-Class/recorder-object.md
+++ /dev/null
@@ -1,51 +0,0 @@
-## `recorder`
-
-Use this object to control VoiceMeeter Banana/Potato's recorder.
-
-### Methods
-
-* #### `ArmBus(index, [onOff])`
-If `onOff` is passed to the method, it switches the recording mode to 1 (bus) and arms/disarms the given bus, otherwise it returns the state of the given bus.
-
-* #### `ArmStrip(index, [onOff])`
-If `onOff` is passed to the method, it switches the recording mode to 0 (strip) and arms/disarms the given strip, otherwise it returns the state of the given strip.
-
-* #### `ArmStrips(index*)`
-Switches the recording mode to 0 (strip), arms the given strips, disarming the others
-
----
-
-### Parameters
-for an up-to-date list of all `recorder` parameters, check out [VBVMR docs](https://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf#page=15)
-
----
-
-### Examples
-#### Set any parameter
-
-```autohotkey
- voicemeeter.recorder.record := true
- voicemeeter.recorder.goto := 90 ; go to 00:01:30
-
- ; if no path is specified, the file is assumed to be in the Documents folder
- voicemeeter.recorder.load := "C:\audio\audioFile.mp3"
-
- ; use bracket syntax for parameters with '.'
- voicemeeter.recorder["mode.PlayOnLoad"] := true
-```
-
-#### Retrieve any parameter
-```autohotkey
- recorder_gain := voicemeeter.recorder.gain
-```
-
-#### Arm/Disarm buses and strips
-```autohotkey
- voicemeeter.recorder.ArmBus(3,true)
- isArmed := voicemeeter.recorder.ArmBus(2)
- voicemeeter.recorder.ArmStrip(1,true)
-
- voicemeeter.recorder.ArmStrip(2,true) ; strip[2] is armed
- voicemeeter.recorder.ArmStrips(1,3,5) ; strip[2] is disarmed and strip[1,3,5] are all armed
-
-```
diff --git a/docs/VMR-Class/vban-object.md b/docs/VMR-Class/vban-object.md
deleted file mode 100644
index f3276a2..0000000
--- a/docs/VMR-Class/vban-object.md
+++ /dev/null
@@ -1,28 +0,0 @@
-## `vban`
-
-Use this object to control VoiceMeeter’s VBAN interface.
-
-### Parameters
-for an up-to-date list of all `vban` parameters, check out [VBVMR docs](https://download.vb-audio.com/Download_CABLE/VoicemeeterRemoteAPI.pdf#page=16)
-
----
-### Examples
-#### Set any parameter
-
-```autohotkey
- voicemeeter.vban.enable := true
-
- voicemeeter.vban.instream[1].ip := "192.168.0.122"
- voicemeeter.vban.instream[1].port := "5959"
- voicemeeter.vban.instream[1].on := true
-
- voicemeeter.vban.outstream[3].name := "defStream"
- voicemeeter.vban.outstream[3].quality := 4
- voicemeeter.vban.outstream[3].bit := 2
-```
-
-#### Retrieve any parameter
-```autohotkey
- stream_channels := voicemeeter.vban.instream[2].channel
-```
-
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index 01fdf8a..901d976 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -1,8 +1,22 @@
-* [Getting started](/ 'VMR.ahk Docs')
-* [VMR Class](/VMR-Class/ 'VMR Class')
- - [Bus/Strip](/VMR-Class/bus-strip-object.md 'Bus/Strip object')
- - [Recorder](/VMR-Class/recorder-object.md 'Recorder object')
- - [VBAN](/VMR-Class/vban-object.md 'VBAN object')
- - [Option](/VMR-Class/option-object.md 'Option object')
- - [command](/VMR-Class/command-object.md 'Command object')
- - [MacroButton](/VMR-Class/macrobutton-object.md 'MacroButton object')
+* [Home](/ 'VMR.ahk Docs')
+
+
+* Classes
+ - [`VMR`](/classes/vmr.md 'VMR Class')
+ - [`VMRBus`](/classes/vmrbus.md 'VMRBus Class')
+ - [`VMRStrip`](/classes/vmrstrip.md 'VMRStrip Class')
+ - [`VMRAsyncOp`](/classes/vmrasyncop.md 'VMRAsyncOp Class')
+ - [`VMRAudioIO`](/classes/vmraudioio.md 'VMRAudioIO Class')
+ - [`VMRDevice`](/classes/vmrdevice.md 'VMRDevice Class')
+ - [`VMRRecorder`](/classes/vmrrecorder.md 'VMRRecorder Class')
+ - [`VMRCommands`](/classes/vmrcommands.md 'VMRCommands Class')
+ - [`VMRMacroButton`](/classes/vmrmacrobutton.md 'VMRMacroButton Class')
+ - [`VMRVBAN`](/classes/vmrvban.md 'VMRVBAN Class')
+ - [`VMRControllerBase`](/classes/vmrcontrollerbase.md 'VMRControllerBase Class')
+ - [`VMRError`](/classes/vmrerror.md 'VMRError Class')
+ - [`VMRUtils`](/classes/vmrutils.md 'VMRUtils Class')
+ - [`VBVMR`](/classes/vbvmr.md 'VBVMR Class')
\ No newline at end of file
diff --git a/docs/classes/vbvmr.md b/docs/classes/vbvmr.md
new file mode 100644
index 0000000..0586b68
--- /dev/null
+++ b/docs/classes/vbvmr.md
@@ -0,0 +1,300 @@
+# `VBVMR`
+
+ A static wrapper class for the Voicemeeter Remote DLL.
+
+ ?> The class must be initialized by calling [`Init()`](/classes/vbvmr?id=static-init) before using any of its static methods.
+
+## Properties
+* #### **Static** `DLL` : `Ptr` :id=static-dll
+ The handle to the loaded Voicemeeter DLL
+* #### **Static** `DLL_PATH` : `String` :id=static-dll-path
+ The path to the loaded Voicemeeter DLL
+
+
+## Methods
+* ### `Init(p_path := "")` :id=static-init
+ Initializes the VBVMR class by loading the Voicemeeter Remote DLL and getting the addresses of all needed functions.
+ If the DLL is already loaded, it returns immediately.
+
+ **Parameters**:
+ - **Optional** `p_path` : `String` - The path to the Voicemeeter Remote DLL. If not specified, it will be looked up in the registry.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the DLL is not found in the specified path or if voicemeeter is not installed.
+
+
+
+______
+* ### `Login()` :id=static-login
+ Opens a Communication Pipe With Voicemeeter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - `0` : OK (no error).
+ - `1` : OK but Voicemeeter is not launched (need to launch it manually).
+
+
+______
+* ### `Logout()` :id=static-logout
+ Closes the Communication Pipe With Voicemeeter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - `0` : OK (no error).
+
+
+______
+* ### `SetParameterFloat(p_prefix, p_parameter, p_value)` :id=static-setparameterfloat
+ Sets the value of a float (numeric) parameter.
+
+ **Parameters**:
+ - `p_prefix` : `String` - The prefix of the parameter, usually the name of the bus/strip (ex: `Bus[0]`).
+
+ - `p_parameter` : `String` - The name of the parameter (ex: `gain`).
+
+ - `p_value` : `Number` - The value to set.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the parameter is not found, or an internal error occurs.
+
+ **Returns**: `Number` - `0` : OK (no error).
+
+
+______
+* ### `SetParameterString(p_prefix, p_parameter, p_value)` :id=static-setparameterstring
+ Sets the value of a string parameter.
+
+ **Parameters**:
+ - `p_prefix` : `String` - The prefix of the parameter, usually the name of the bus/strip (ex: `Strip[1]`).
+
+ - `p_parameter` : `String` - The name of the parameter (ex: `name`).
+
+ - `p_value` : `String` - The value to set.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the parameter is not found, or an internal error occurs.
+
+ **Returns**: `Number` - `0` : OK (no error).
+
+
+______
+* ### `GetParameterFloat(p_prefix, p_parameter)` :id=static-getparameterfloat
+ Returns the value of a float (numeric) parameter.
+
+ **Parameters**:
+ - `p_prefix` : `String` - The prefix of the parameter, usually the name of the bus/strip (ex: `Bus[2]`).
+
+ - `p_parameter` : `String` - The name of the parameter (ex: `gain`).
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the parameter is not found, or an internal error occurs.
+
+ **Returns**: `Number` - The value of the parameter.
+
+
+______
+* ### `GetParameterString(p_prefix, p_parameter)` :id=static-getparameterstring
+ Returns the value of a string parameter.
+
+ **Parameters**:
+ - `p_prefix` : `String` - The prefix of the parameter, usually the name of the bus/strip (ex: `Strip[1]`).
+
+ - `p_parameter` : `String` - The name of the parameter (ex: `name`).
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the parameter is not found, or an internal error occurs.
+
+ **Returns**: `String` - The value of the parameter.
+
+
+______
+* ### `GetLevel(p_type, p_channel)` :id=static-getlevel
+ Returns the level of a single bus/strip channel.
+
+ **Parameters**:
+ - `p_type` : `Number` - The type of the returned level
+ - `0`: pre-fader
+ - `1`: post-fader
+ - `2`: post-mute
+ - `3`: output-levels
+
+ - `p_channel` : `Number` - The channel's zero-based index.
+ - Channel Indices depend on the type of voiceemeeter running.
+ - Channel Indices are incremented from the left to right (On the Voicemeeter UI), starting at `0`, Buses and Strips have separate Indices (see `p_type`).
+ - Physical (hardware) strips have 2 channels (left, right), Buses and virtual strips have 8 channels.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the channel index is invalid, or an internal error occurs.
+
+ **Returns**: `Number` - The level of the requested channel.
+
+
+______
+* ### `GetVoicemeeterType()` :id=static-getvoicemeetertype
+ Returns the type of Voicemeeter running.
+
+
+ **See also**:
+ [`VMR.Types`](/classes/vmr?id=static-types) for possible values.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - The type of Voicemeeter running.
+
+
+______
+* ### `GetVoicemeeterVersion()` :id=static-getvoicemeeterversion
+ Returns the version of Voicemeeter running.
+ - The version is returned as a 4-part string (v1.v2.v3.v4)
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `String` - The version of Voicemeeter running.
+
+
+______
+* ### `Output_GetDeviceNumber()` :id=static-output_getdevicenumber
+ Returns the number of Output Devices available on the system.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - The number of output devices.
+
+
+______
+* ### `Output_GetDeviceDesc(p_index)` :id=static-output_getdevicedesc
+ Returns the Descriptor of an output device.
+
+ **Parameters**:
+ - `p_index` : `Number` - The index of the device (zero-based).
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: [`VMRDevice`](/classes/vmrdevice) - An object containing the `Name`, `Driver` and `Hwid` of the device.
+
+
+______
+* ### `Input_GetDeviceNumber()` :id=static-input_getdevicenumber
+ Returns the number of Input Devices available on the system.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - The number of input devices.
+
+
+______
+* ### `Input_GetDeviceDesc(p_index)` :id=static-input_getdevicedesc
+ Returns the Descriptor of an input device.
+
+ **Parameters**:
+ - `p_index` : `Number` - The index of the device (zero-based).
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: [`VMRDevice`](/classes/vmrdevice) - An object containing the `Name`, `Driver` and `Hwid` of the device.
+
+
+______
+* ### `IsParametersDirty()` :id=static-isparametersdirty
+ Checks if any parameters have changed.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - `0` : No change
+ - `1` : Some parameters have changed
+
+
+______
+* ### `MacroButton_GetStatus(p_logicalButton, p_bitMode)` :id=static-macrobutton_getstatus
+ Returns the current status of a given button.
+
+ **Parameters**:
+ - `p_logicalButton` : `Number` - The index of the button (zero-based).
+
+ - `p_bitMode` : `Number` - The type of the returned value.
+ - `0`: button-state
+ - `2`: displayed-state
+ - `3`: trigger-state
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - The status of the button
+ - `0`: Off
+ - `1`: On
+
+
+______
+* ### `MacroButton_SetStatus(p_logicalButton, p_value, p_bitMode)` :id=static-macrobutton_setstatus
+ Sets the status of a given button.
+
+ **Parameters**:
+ - `p_logicalButton` : `Number` - The index of the button (zero-based).
+
+ - `p_value` : `Number` - The value to set.
+ - `0`: Off
+ - `1`: On
+
+ - `p_bitMode` : `Number` - The type of the returned value.
+ - `0`: button-state
+ - `2`: displayed-state
+ - `3`: trigger-state
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - The status of the button
+ - `0`: Off
+ - `1`: On
+
+
+______
+* ### `MacroButton_IsDirty()` :id=static-macrobutton_isdirty
+ Checks if any Macro Buttons states have changed.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - `0` : No change
+ - `> 0` : Some buttons have changed
+
+
+______
+* ### `GetMidiMessage()` :id=static-getmidimessage
+ Returns any available MIDI messages from Voicemeeter's MIDI mapping.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Array` - `[0xF0, 0xFF, ...]` An array of hex-formatted bytes that compose one or more MIDI messages, or an empty string `""` if no messages are available.
+ - A single message is usually 2 or 3 bytes long
+ - The returned array will contain at most `1024` bytes.
+
+
+______
+* ### `SetParameters(p_script)` :id=static-setparameters
+ Sets one or more parameters using a voicemeeter script.
+
+ **Parameters**:
+ - `p_script` : `String` - The script to execute (must be less than `48kb`).
+ - Scripts can contain one or more parameter changes
+ - Changes can be seperated by a new line, `;` or `,`.
+ - Indices inside the script are zero-based.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: `Number` - `0` : OK (no error)
+ - `> 0` : Number of the line causing an error
+
+## Notes
+While this class can be used to directly call the Voicemeeter Remote DLL functions, it's recommended to use the `VMR` class instead of this one, as it simplifies usage of the API.
\ No newline at end of file
diff --git a/docs/classes/vmr.md b/docs/classes/vmr.md
new file mode 100644
index 0000000..114e55b
--- /dev/null
+++ b/docs/classes/vmr.md
@@ -0,0 +1,142 @@
+# `VMR`
+
+ A wrapper class for Voicemeeter Remote that hides the low-level API to simplify usage.
+ Must be initialized by calling [`Login()`](/classes/vmr?id=login) after creating the VMR instance.
+## Constructor `__New(p_path := "")` :id=constructor
+ Creates a new VMR instance and initializes the [`VBVMR`](/classes/vbvmr) class.
+
+ **Parameters**:
+ - **Optional** `p_path` : `String` - The path to the Voicemeeter Remote DLL. If not specified, VBVMR will attempt to find it in the registry.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the DLL is not found in the specified path or if voicemeeter is not installed.
+
+
+## Properties
+* #### `Type` : `VMR.Types` :id=type
+ The type of Voicemeeter that is currently running.
+
+ **See also**:
+ [`VMR.Types`](/classes/vmr?id=static-types) for a list of available types.
+* #### `Version` : `String` :id=version
+ The version of Voicemeeter that is currently running.
+ The AHK function [`VerCompare`](https://www.autohotkey.com/docs/v2/lib/VerCompare.htm) can be used to compare version strings.
+* #### `Bus` : `Array` :id=bus
+ An array of voicemeeter buses
+* #### `Strip` : `Array` :id=strip
+ An array of voicemeeter strips
+* #### `Command` : [`VMRCommands`](/classes/vmrcommands) :id=command
+ Commands that control various aspects of Voicemeeter
+
+ **See also**:
+ [`VMRCommands`](/classes/vmrcommands) for a list of available commands.
+* #### `Fx` : [`VMRControllerBase`](/classes/vmrcontrollerbase) :id=fx
+ Controls Voicemeeter Potato's FX settings
+
+ ?> This property is only available when running Voicemeeter Potato (`VMR.Type == VMR.Types.Potato`).
+* #### `Patch` : [`VMRControllerBase`](/classes/vmrcontrollerbase) :id=patch
+ Controls Voicemeeter's Patch parameters
+* #### `Option` : [`VMRControllerBase`](/classes/vmrcontrollerbase) :id=option
+ Controls Voicemeeter's System Settings
+* #### `MacroButton` : [`VMRMacroButton`](/classes/vmrmacrobutton) :id=macrobutton
+ Controls Voicemeeter's Macro Buttons app
+* #### `Recorder` : [`VMRRecorder`](/classes/vmrrecorder) :id=recorder
+ Controls Voicemeeter's Recorder
+ ?> This property is only available when running Voicemeeter Banana or Potato (`VMR.Type.Id == 2 || VMR.Type.Id == 3`).
+
+## Methods
+* ### `Login(p_launchVoicemeeter := true)` :id=login
+ Initializes the VMR instance and opens the communication pipe with Voicemeeter.
+
+ **Parameters**:
+ - **Optional** `p_launchVoicemeeter` : `Boolean` - Whether to launch Voicemeeter if it's not already running. Defaults to `true`.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
+ **Returns**: [`VMR`](/classes/vmr) - The `VMR` instance.
+
+
+______
+* ### `RunVoicemeeter(p_type := unset)` :id=runvoicemeeter
+ Attempts to run Voicemeeter.
+ When passing a `p_type`, it will only attempt to run the specified Voicemeeter type,
+ otherwise it will attempt to run every voicemeeter type descendingly until one is successfully launched.
+
+ **Parameters**:
+ - **Optional** `p_type` : `Number` - The type of Voicemeeter to run.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the specified Voicemeeter type is invalid, or if no Voicemeeter type could be launched.
+
+ **Returns**: `Number` - The PID of the launched Voicemeeter process.
+
+
+______
+* ### `On(p_event, p_listener)` :id=on
+ Registers a callback function to be called when the specified event is fired.
+
+
+ **See also**:
+ `VMRConsts.Events` for a list of available events.
+
+ **Parameters**:
+ - `p_event` : `String` - The name of the event to listen for.
+
+ - `p_listener` : `Func` - The function to call when the event is fired.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the specified event is invalid, or if the listener is not a valid `Func` object.
+
+
+
+______
+* ### `Off(p_event, p_listener := unset)` :id=off
+ Removes a callback function from the specified event.
+
+
+ **See also**:
+ `VMRConsts.Events` for a list of available events.
+
+ **Parameters**:
+ - `p_event` : `String` - The name of the event.
+
+ - **Optional** `p_listener` : `Func` - The function to remove, if omitted, all listeners for the specified event will be removed.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If the specified event is invalid, or if the listener is not a valid `Func` object.
+
+ **Returns**: `Boolean` - Whether the listener was removed.
+
+
+______
+* ### `Sync()` :id=sync
+ Synchronizes the VMR instance with Voicemeeter.
+
+ **Returns**: `Boolean` - Whether voicemeeter state has changed since the last sync.
+
+
+______
+* ### `Exec(p_script)` :id=exec
+ Executes a Voicemeeter script (**not** an AutoHotkey script).
+ - Scripts can contain one or more parameter changes
+ - Changes can be seperated by a new line, `;` or `,`.
+ - Indices in the script are zero-based.
+
+ **Parameters**:
+ - `p_script` : `String` - The script to execute.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an error occurs while executing the script.
+
+
+______
+* ### `UpdateDevices(p_wParam := unset, *)` :id=updatedevices
+ Updates the list of strip/bus devices.
+
+ **Parameters**:
+ - **Optional** `p_wParam` : `Number` - If passed, must be equal to `VMRConsts.WM_DEVICE_CHANGE_PARAM` to update the device arrays.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs.
+
diff --git a/docs/classes/vmrasyncop.md b/docs/classes/vmrasyncop.md
new file mode 100644
index 0000000..1dfddd4
--- /dev/null
+++ b/docs/classes/vmrasyncop.md
@@ -0,0 +1,50 @@
+# `VMRAsyncOp`
+
+ A basic wrapper for an async operation.
+
+ This is needed because the VMR API is asynchronous which means that operations like `SetFloatParameter` do not take effect immediately,
+ and so if the same parameter was fetched right after it was set, the old value would be returned (or sometimes it would return a completely invalid value).
+
+ And unfortunately, the VMR API does not provide any meaningful way to wait for a particular operation to complete (callbacks, synchronous api), and so this class uses a normal timer to wait for the operation to complete.
+## Constructor `__New(p_supplier := unset, p_autoResolveTimeout := unset)` :id=constructor
+ Creates a new async operation.
+
+ **Parameters**:
+ - **Optional** `p_supplier` : `() => Any` - Supplies the result of the async operation.
+
+ - **Optional** `p_autoResolveTimeout` : `Number` - Automatically resolves the async operation after the specified number of milliseconds.
+
+
+## Properties
+* #### **Static** `Empty` : `VMRAsyncOp` :id=static-empty
+ Returns an empty async operation that's already been resolved.
+* #### **Static** `DEFAULT_DELAY` : `Number` :id=static-default_delay
+ The default delay that's used when awaiting the async operation.
+* #### `IsEmpty` : `Boolean` :id=isempty
+ Whether the operation is an empty operation returned by `VMRAsyncOp.Empty`.
+* #### `Resolved` : `Boolean` :id=resolved
+ Whether the operation has already been resolved.
+
+## Methods
+* ### `Then(p_listener, p_innerOpDelay := 0)` :id=then
+ Adds a listener to the async operation.
+
+ **Parameters**:
+ - `p_listener` : `(Any) => Any` - A function that will be called when the async operation is resolved.
+
+ - **Optional** `p_innerOpDelay` : `Number` - If passed, the returned async operation will be delayed by the specified number of milliseconds.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - if `p_listener` is not a function or has an invalid number of parameters.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - a new async operation that will be resolved when the current operation is resolved and the listener is called.
+
+
+______
+* ### `Await(p_timeoutMs := 0)` :id=await
+ Waits for the async operation to be resolved.
+
+ **Parameters**:
+ - **Optional** `p_timeoutMs` : `Number` - The maximum number of milliseconds to wait before throwing an error.
+
+ **Returns**: `Any` - The result of the async operation.
\ No newline at end of file
diff --git a/docs/classes/vmraudioio.md b/docs/classes/vmraudioio.md
new file mode 100644
index 0000000..b868050
--- /dev/null
+++ b/docs/classes/vmraudioio.md
@@ -0,0 +1,109 @@
+# `VMRAudioIO`
+
+ A base class for [`VMRBus`](/classes/vmrbus) and [`VMRStrip`](/classes/vmrstrip)
+## Constructor `__New(p_index, p_ioType)` :id=constructor
+ Creates a new `VMRAudioIO` object.
+
+ **Parameters**:
+ - `p_index` : `Number` - The zero-based index of the bus/strip.
+
+ - `p_ioType` : `String` - The type of the object. (`Bus` or `Strip`)
+
+
+## Properties
+* #### `GainPercentage` : `Number` :id=gainpercentage
+ Gets/Sets the gain as a percentage
+* #### `Device` : [`VMRDevice`](/classes/vmrdevice) :id=device
+ Gets/Sets the object's current device
+* #### `GainLimit` : `Number` :id=gainlimit
+ The object's upper gain limit
+* #### `Level` : `Array` :id=level
+ An array of the object's channel levels
+* #### `Id` : `String` :id=id
+ The object's identifier that's used when calling VMR's functions.
+ ex: `Bus[0]` or `Strip[3]`
+* #### `Index` : `Number` :id=index
+ The object's one-based index
+* #### `Type` : `String` :id=type
+ The object's type (`Bus` or `Strip`)
+
+## Methods
+* ### `SetParameter(p_name, p_value)` :id=setparameter
+ Sets the value of a parameter.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the parameter.
+
+ - `p_value` : `Any` - The value of the parameter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - An async operation that resolves to `true` if the parameter was set successfully.
+
+
+______
+* ### `GetParameter(p_name)` :id=getparameter
+ Returns the value of a parameter.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the parameter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: `Any` - The value of the parameter.
+
+
+______
+* ### `Increment(p_param, p_amount)` :id=increment
+ Increments a parameter by a specific amount.
+ - It's recommended to use this method instead of incrementing the parameter directly (`++vm.Bus[1].Gain`).
+ - Since this method doesn't fetch the current value of the parameter to update it, [`GainLimit`](/classes/vmraudioio?id=gainlimit) cannot be applied here.
+
+ **Parameters**:
+ - `p_param` : `String` - The name of the parameter, must be a numeric parameter.
+
+ - `p_amount` : `Number` - The amount to increment the parameter by, can be set to a negative value to decrement instead.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - An async operation that resolves with the incremented value.
+
+
+______
+* ### `FadeTo(p_db, p_duration)` :id=fadeto
+ Sets the gain to a specific value with a progressive fade.
+
+ **Parameters**:
+ - `p_db` : `Number` - The gain value in dBs.
+
+ - `p_duration` : `Number` - The duration of the fade in milliseconds.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - An async operation that resolves with the final gain value.
+
+
+______
+* ### `FadeBy(p_dbAmount, p_duration)` :id=fadeby
+ Fades the gain by a specific amount.
+
+ **Parameters**:
+ - `p_dbAmount` : `Number` - The amount to fade the gain by in dBs.
+
+ - `p_duration` : `Number` - The duration of the fade in milliseconds.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - An async operation that resolves with the final gain value.
+
+
+______
+* ### `IsPhysical()` :id=isphysical
+ Returns `true` if the bus/strip is a physical (hardware) one.
+
+ **Returns**: `Boolean`
\ No newline at end of file
diff --git a/docs/classes/vmrbus.md b/docs/classes/vmrbus.md
new file mode 100644
index 0000000..4887b40
--- /dev/null
+++ b/docs/classes/vmrbus.md
@@ -0,0 +1,34 @@
+# `VMRBus`
+
+ A wrapper class for voicemeeter buses.
+
+#### Extends: [`VMRAudioIO`](/classes/vmraudioio)
+## Constructor `__New(p_index, p_vmrType)` :id=constructor
+ Creates a new VMRBus object.
+
+ **Parameters**:
+ - `p_index` : `Number` - The zero-based index of the bus.
+
+ - `p_vmrType` : `Number` - The type of the running voicemeeter.
+
+
+## Properties
+* #### **Static** `Devices` : `Array` :id=static-devices
+ An array of bus (output) devices
+* #### `Name` : `String` :id=name
+ The bus's name (as shown in voicemeeter's UI)
+
+## Methods
+* ### `GetDevice(p_name, p_driver := unset)` :id=static-getdevice
+ Retrieves a bus (output) device by its name/driver.
+
+
+ **See also**:
+ `VMRConsts.DEVICE_DRIVERS` for a list of valid drivers.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the device.
+
+ - **Optional** `p_driver` : `String` - The driver of the device, If omitted, `VMRConsts.DEFAULT_DEVICE_DRIVER` will be used.
+
+ **Returns**: [`VMRDevice`](/classes/vmrdevice) - A device object, or an empty string `""` if the device was not found.
\ No newline at end of file
diff --git a/docs/classes/vmrcommands.md b/docs/classes/vmrcommands.md
new file mode 100644
index 0000000..65ca830
--- /dev/null
+++ b/docs/classes/vmrcommands.md
@@ -0,0 +1,142 @@
+# `VMRCommands`
+
+ Write-only actions that control voicemeeter
+
+## Properties
+* #### `Button` : `Any` :id=button
+ Sets a macro button's parameter
+
+## Methods
+* ### `Restart()` :id=restart
+ Restarts the Audio Engine
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Shutdown()` :id=shutdown
+ Shuts down Voicemeeter
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Show(p_open := true)` :id=show
+ Shows the Voicemeeter window
+
+ **Parameters**:
+ - **Optional** `p_open` : `Boolean` - `true` to show the window, `false` to hide it
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Lock(p_state := true)` :id=lock
+ Locks the Voicemeeter UI
+
+ **Parameters**:
+ - **Optional** `p_state` : `number` - `true` to lock the UI, `false` to unlock it
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Eject()` :id=eject
+ Ejects the recorder's cassette
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Reset()` :id=reset
+ Resets all voicemeeeter configuration
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Save(p_filePath)` :id=save
+ Saves the current configuration to a file
+
+ **Parameters**:
+ - `p_filePath` : `String` - The path to save the configuration to
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `Load(p_filePath)` :id=load
+ Loads configuration from a file
+
+ **Parameters**:
+ - `p_filePath` : `String` - The path to load the configuration from
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `ShowVBANChat(p_show := true)` :id=showvbanchat
+ Shows the VBAN chat dialog
+
+ **Parameters**:
+ - **Optional** `p_show` : `Boolean` - `true` to show the dialog, `false` to hide it
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `SaveBusEQ(p_busIndex, p_filePath)` :id=savebuseq
+ Saves a bus's EQ settings to a file
+
+ **Parameters**:
+ - `p_busIndex` : `Number` - The one-based index of the bus to save
+
+ - `p_filePath` : `String` - The path to save the EQ settings to
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `LoadBusEQ(p_busIndex, p_filePath)` :id=loadbuseq
+ Loads a bus's EQ settings from a file
+
+ **Parameters**:
+ - `p_busIndex` : `Number` - The one-based index of the bus to load
+
+ - `p_filePath` : `String` - The path to load the EQ settings from
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `SaveStripEQ(p_stripIndex, p_filePath)` :id=savestripeq
+ Saves a strip's EQ settings to a file
+
+ **Parameters**:
+ - `p_stripIndex` : `Number` - The one-based index of the strip to save
+
+ - `p_filePath` : `String` - The path to save the EQ settings to
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `LoadStripEQ(p_stripIndex, p_filePath)` :id=loadstripeq
+ Loads a strip's EQ settings from a file
+
+ **Parameters**:
+ - `p_stripIndex` : `Number` - The one-based index of the strip to load
+
+ - `p_filePath` : `String` - The path to load the EQ settings from
+
+ **Returns**: `Boolean` - true if the command was successful
+
+
+______
+* ### `RecallPreset(p_preset)` :id=recallpreset
+ Recalls a Preset Scene
+
+ **Parameters**:
+ - `p_preset` : `String | Number` - The name of the preset to recall or its one-based index
+
+ **Returns**: `Boolean` - true if the command was successful
\ No newline at end of file
diff --git a/docs/classes/vmrcontrollerbase.md b/docs/classes/vmrcontrollerbase.md
new file mode 100644
index 0000000..0fefa29
--- /dev/null
+++ b/docs/classes/vmrcontrollerbase.md
@@ -0,0 +1,29 @@
+# `VMRControllerBase`
+
+
+## Methods
+* ### `SetParameter(p_name, p_value)` :id=setparameter
+ Sets the value of a parameter.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the parameter.
+
+ - `p_value` : `Any` - The value of the parameter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: [`VMRAsyncOp`](/classes/vmrasyncop) - An async operation that resolves to `true` if the parameter was set successfully.
+
+
+______
+* ### `GetParameter(p_name)` :id=getparameter
+ Returns the value of a parameter.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the parameter.
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If invalid parameters are passed or if an internal error occurs.
+
+ **Returns**: `Any` - The value of the parameter.
diff --git a/docs/classes/vmrdevice.md b/docs/classes/vmrdevice.md
new file mode 100644
index 0000000..48a0509
--- /dev/null
+++ b/docs/classes/vmrdevice.md
@@ -0,0 +1,10 @@
+# `VMRDevice`
+
+
+## Properties
+* #### `Name` : `String` :id=name
+ The device's name
+* #### `Hwid` : `String` :id=hwid
+ The device's hardware id
+* #### `Driver` : `String` :id=driver
+ The device's driver, See `VMRConsts.DEVICE_DRIVERS` for a list of valid drivers.
diff --git a/docs/classes/vmrerror.md b/docs/classes/vmrerror.md
new file mode 100644
index 0000000..1599fb5
--- /dev/null
+++ b/docs/classes/vmrerror.md
@@ -0,0 +1,14 @@
+# `VMRError`
+
+
+#### Extends: `Error`
+
+## Properties
+* #### `ReturnCode` : `Number` :id=returncode
+ The return code of the Voicemeeter function that failed
+* #### `What` : `String` :id=what
+ The name of the function that threw the error
+* #### `Message` : `String` :id=message
+ An error message
+* #### `Extra` : `String` :id=extra
+ Extra information about the error
\ No newline at end of file
diff --git a/docs/classes/vmrmacrobutton.md b/docs/classes/vmrmacrobutton.md
new file mode 100644
index 0000000..5de23b7
--- /dev/null
+++ b/docs/classes/vmrmacrobutton.md
@@ -0,0 +1,60 @@
+# `VMRMacroButton`
+
+
+
+## Methods
+* ### `Run()` :id=run
+ Runs the Voicemeeter Macro Buttons application.
+
+
+______
+* ### `Show(p_show := true)` :id=show
+ Shows/Hides the Voicemeeter Macro Buttons application.
+
+ **Parameters**:
+ - **Optional** `p_show` : `Boolean` - Whether to show or hide the application
+
+
+
+______
+* ### `SetStatus(p_index, p_value, p_bitMode := 0)` :id=setstatus
+ Sets the status of a given button.
+
+ **Parameters**:
+ - `p_index` : `Number` - The one-based index of the button
+
+ - `p_value` : `Number` - The value to set
+ - `0`: Off
+ - `1`: On
+
+ - **Optional** `p_bitMode` : `Number` - The type of the returned value
+ - `0`: button-state
+ - `2`: displayed-state
+ - `3`: trigger-state
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs
+
+ **Returns**: `Number` - The status of the button
+ - `0`: Off
+ - `1`: On
+
+
+______
+* ### `GetStatus(p_index, p_bitMode := 0)` :id=getstatus
+ Gets the status of a given button.
+
+ **Parameters**:
+ - `p_index` : `Number` - The one-based index of the button
+
+ - **Optional** `p_bitMode` : `Number` - The type of the returned value
+ - `0`: button-state
+ - `2`: displayed-state
+ - `3`: trigger-state
+
+ **Throws**:
+ - [`VMRError`](/classes/vmrerror) - If an internal error occurs
+
+ **Returns**: `Number` - The status of the button
+ - `0`: Off
+ - `1`: On
\ No newline at end of file
diff --git a/docs/classes/vmrrecorder.md b/docs/classes/vmrrecorder.md
new file mode 100644
index 0000000..efde902
--- /dev/null
+++ b/docs/classes/vmrrecorder.md
@@ -0,0 +1,29 @@
+# `VMRRecorder`
+
+
+
+#### Extends: [`VMRControllerBase`](/classes/vmrcontrollerbase)
+
+## Properties
+* #### `ArmBus` : `Boolean` :id=armbus
+ Arms the specified bus for recording, switching the recording mode to `1` (bus).
+ Or returns the state of the specified bus (whether it's armed or not).
+* #### `ArmStrip` : `Boolean` :id=armstrip
+ Arms the specified strip for recording, switching the recording mode to `0` (strip).
+ Or returns the state of the specified strip (whether it's armed or not).
+
+## Methods
+* ### `ArmStrips(p_strips)` :id=armstrips
+ Arms the specified strips for recording, switching the recording mode to `0` (strip) and disarming any armed strips.
+
+ **Parameters**:
+ - `p_strips` : `Array` - The strips' one-based indices.
+
+
+
+______
+* ### `Load(p_path)` :id=load
+ Loads a file into the recorder.
+
+ **Parameters**:
+ - `p_path` : `String`
\ No newline at end of file
diff --git a/docs/classes/vmrstrip.md b/docs/classes/vmrstrip.md
new file mode 100644
index 0000000..7243db7
--- /dev/null
+++ b/docs/classes/vmrstrip.md
@@ -0,0 +1,38 @@
+# `VMRStrip`
+
+ A wrapper class for voicemeeter strips.
+
+#### Extends: [`VMRAudioIO`](/classes/vmraudioio)
+## Constructor `__New(p_index, p_vmrType)` :id=constructor
+ Creates a new VMRStrip object.
+
+ **Parameters**:
+ - `p_index` : `Number` - The zero-based index of the strip.
+
+ - `p_vmrType` : `Number` - The type of the running voicemeeter.
+
+
+## Properties
+* #### **Static** `Devices` : `Array` :id=static-devices
+ An array of strip (input) devices
+* #### `AppGain` : `Number` :id=appgain
+ Sets an application's gain on the strip.
+* #### `AppMute` : `Boolean` :id=appmute
+ Sets an application's mute state on the strip.
+* #### `Name` : `String` :id=name
+ The strip's name (as shown in voicemeeter's UI)
+
+## Methods
+* ### `GetDevice(p_name, p_driver := unset)` :id=static-getdevice
+ Retrieves a strip (input) device by its name/driver.
+
+
+ **See also**:
+ `VMRConsts.DEVICE_DRIVERS` for a list of valid drivers.
+
+ **Parameters**:
+ - `p_name` : `String` - The name of the device.
+
+ - **Optional** `p_driver` : `String` - The driver of the device, If omitted, `VMRConsts.DEFAULT_DEVICE_DRIVER` will be used.
+
+ **Returns**: [`VMRDevice`](/classes/vmrdevice) - A device object, or an empty string `""` if the device was not found.
\ No newline at end of file
diff --git a/docs/classes/vmrutils.md b/docs/classes/vmrutils.md
new file mode 100644
index 0000000..b1d0628
--- /dev/null
+++ b/docs/classes/vmrutils.md
@@ -0,0 +1,72 @@
+# `VMRUtils`
+
+
+
+## Methods
+* ### `DbToPercentage(p_dB)` :id=static-dbtopercentage
+ Converts a dB value to a percentage value.
+
+ **Parameters**:
+ - `p_dB` : `Number` - The dB value to convert.
+
+ **Returns**: `Number` - The percentage value.
+
+
+______
+* ### `PercentageToDb(p_percentage)` :id=static-percentagetodb
+ Converts a percentage value to a dB value.
+
+ **Parameters**:
+ - `p_percentage` : `Number` - The percentage value to convert.
+
+ **Returns**: `Number` - The dB value.
+
+
+______
+* ### `EnsureBetween(p_value, p_min, p_max)` :id=static-ensurebetween
+ Applies an upper and a lower bound on a passed value.
+
+ **Parameters**:
+ - `p_value` : `Number` - The value to apply the bounds on.
+
+ - `p_min` : `Number` - The lower bound.
+
+ - `p_max` : `Number` - The upper bound.
+
+ **Returns**: `Number` - The value with the bounds applied.
+
+
+______
+* ### `IndexOf(p_array, p_value)` :id=static-indexof
+ Returns the index of the first occurrence of a value in an array, or -1 if it's not found.
+
+ **Parameters**:
+ - `p_array` : `Array` - The array to search in.
+
+ - `p_value` : `Any` - The value to search for.
+
+ **Returns**: `Number` - The index of the first occurrence of the value in the array, or -1 if it's not found.
+
+
+______
+* ### `Join(p_params, p_seperator, p_maxLength := 30)` :id=static-join
+ Returns a string with the passed parameters joined using the passed seperator.
+
+ **Parameters**:
+ - `p_params` : `Array` - The parameters to join.
+
+ - `p_seperator` : `String` - The seperator to use.
+
+ - **Optional** `p_maxLength` : `Number` - The maximum length of each parameter.
+
+ **Returns**: `String` - The joined string.
+
+
+______
+* ### `ToString(p_value)` :id=static-tostring
+ Converts a value to a string.
+
+ **Parameters**:
+ - `p_value` : `Any` - The value to convert to a string.
+
+ **Returns**: `String` - The string representation of the passed value
\ No newline at end of file
diff --git a/docs/classes/vmrvban.md b/docs/classes/vmrvban.md
new file mode 100644
index 0000000..4514ae8
--- /dev/null
+++ b/docs/classes/vmrvban.md
@@ -0,0 +1,11 @@
+# `VMRVBAN`
+
+
+
+#### Extends: [`VMRControllerBase`](/classes/vmrcontrollerbase)
+
+## Properties
+* #### `Instream` : [`VMRControllerBase`](/classes/vmrcontrollerbase) :id=instream
+ Controls a VBAN input stream
+* #### `Outstream` : [`VMRControllerBase`](/classes/vmrcontrollerbase) :id=outstream
+ Controls a VBAN output stream
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index 257f252..02c8f76 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -2,38 +2,66 @@
-
- VMR.ahk Docs
-
-
-
-
-
-
-
+
+ VMR.ahk Docs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+