From 0c49d0e42bd3e2df71ae534f1a727eabed200842 Mon Sep 17 00:00:00 2001
From: Saif Alaqqad
Date: Sat, 25 Nov 2023 14:31:00 +0300
Subject: [PATCH] Update examples + README for v2
---
README.md | 41 +-
ahkpm.json | 2 +-
dist/VMR.ahk | 1360 +++++++++++++++++++++++++
examples/README.md | 10 +-
examples/hotkey_example.ahk | 101 +-
examples/level_stabilizer_example.ahk | 76 +-
examples/midi_message_example.ahk | 19 +-
examples/script_example.ahk | 36 +-
examples/ui_example.ahk | 109 --
9 files changed, 1530 insertions(+), 224 deletions(-)
create mode 100644 dist/VMR.ahk
delete mode 100644 examples/ui_example.ahk
diff --git a/README.md b/README.md
index bdc5a07..4e6cd44 100644
--- a/README.md
+++ b/README.md
@@ -6,31 +6,40 @@ VMR.ahk
## 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
+2. Run `ahkpm include gh:SaifAqqad/VMR.ahk -f my-script.ahk` to add an include directive in your script
+ ###### Replace *my-script.ahk* with your script's path
### 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
-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 `
+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/v2/Scripts.htm#lib) and use `#Include `
+
+**Note: The current version of VMR only works with AHK v2, The AHK v1 version is available on the [v1 branch](https://github.com/SaifAqqad/VMR.ahk/tree/v1)**
+
+## Usage
-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++
- ```
+- Create an instance of the VMR class and log in to the API:
+ ```ahk
+ voicemeeter := VMR().login()
+ ```
+- 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++
+ ```
+
+ ##### For more info, check out the [documentation](https://saifaqqad.github.io/VMR.ahk/)
## 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 manually:
+
```powershell
-.\Build.ahk
+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..81b64ca 100644
--- a/ahkpm.json
+++ b/ahkpm.json
@@ -1,5 +1,5 @@
{
- "version": "1.2.0",
+ "version": "2.0.0-alpha",
"description": "AutoHotkey wrapper class for Voicemeeter's Remote API",
"repository": "https://github.com/SaifAqqad/VMR.ahk",
"website": "https://saifaqqad.github.io/VMR.ahk",
diff --git a/dist/VMR.ahk b/dist/VMR.ahk
new file mode 100644
index 0000000..d35d3c9
--- /dev/null
+++ b/dist/VMR.ahk
@@ -0,0 +1,1360 @@
+/**
+ * VMR.ahk - A wrapper for Voicemeeter's Remote API
+ * - Version 2.0.0-alpha
+ * - Build timestamp 2023-11-25 10:59:30 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
+ /**
+ * Covnerts 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)
+ }
+ /**
+ * 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) + 0
+ }
+ /**
+ * 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) => Max(p_min, Min(p_max, p_value))
+ /**
+ * 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) {
+ 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 {
+ __New(p_errorValue, p_funcName, p_funcParams*) {
+ this.returnCode := ""
+ 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"
+ }
+ /**
+ * Known Voicemeeter types
+ * @type {Array} - Array of voiceemeeter type descriptors
+ * __________
+ * @typedef {{id, name, executable, busCount, stripCount, vbanCount}} VoicemeeterType
+ */
+ static VOICEMEETER_TYPES := [{
+ id: 1,
+ name: "Voicemeeter",
+ executable: "Voicemeeter.exe",
+ busCount: 2,
+ vbanCount: 4,
+ stripCount: 3
+ }, {
+ id: 2,
+ name: "Voicemeeter Banana",
+ executable: "voicemeeterpro.exe",
+ busCount: 5,
+ vbanCount: 8,
+ stripCount: 5
+ }, {
+ id: 3,
+ name: "Voicemeeter Potato",
+ executable: Format("voicemeeter8{}.exe", A_Is64bitOS ? "x64" : ""),
+ busCount: 8,
+ vbanCount: 8,
+ stripCount: 8
+ }]
+ /**
+ * 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
+}
+class VMRDevice {
+ __New(name, driver) {
+ this.name := name
+ if (IsNumber(driver)) {
+ switch driver {
+ case 3:
+ driver := "wdm"
+ case 4:
+ driver := "ks"
+ case 5:
+ driver := "asio"
+ default:
+ driver := "mme"
+ }
+ }
+ this.driver := driver
+ }
+}
+/**
+ * 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,
+ 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")
+ }
+ }
+ /**
+ * @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
+ }
+ /**
+ * 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 VMRConsts.VOICEMEETER_TYPES|`VMRConsts.VOICEMEETER_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 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` and `driver` of the device.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Output_GetDeviceDesc(p_index) {
+ local result, name := Buffer(1024),
+ driver := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.Output_GetDeviceDescW, "Int", p_index, "Ptr", driver, "Ptr", name, "Ptr", 0, "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"))
+ }
+ /**
+ * 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` and `driver` of the device.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ static Input_GetDeviceDesc(p_index) {
+ local result, name := Buffer(1024),
+ driver := Buffer(4)
+ try result := DllCall(VBVMR.FUNC.Input_GetDeviceDescW, "Int", p_index, "Ptr", driver, "Ptr", name, "Ptr", 0, "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"))
+ }
+ /**
+ * 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 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 := 12
+ /**
+ * 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))
+ }
+ /**
+ * 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()
+ /**
+ * 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 "]"
+ }
+ /**
+ * @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 "]" : "." param
+ }
+ }
+ 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 {Any} - If the parameter was set successfully it returns `p_value`, otherwise it returns `""`.
+ * @throws {VMRError} - If an internal error occurs.
+ */
+ _Set(p_key, p_params, p_value) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return ""
+ if (p_params.Length > 0) {
+ for param in p_params {
+ p_key .= IsNumber(param) ? "[" param "]" : "." param
+ }
+ }
+ return this.SetParameter(p_key, p_value) ? p_value : ""
+ }
+ /**
+ * 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 {Boolean} - `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 false
+ local vmrFunc := VMRAudioIO._IsStringParam(p_name) ? VBVMR.SetParameterString.Bind(VBVMR) : VBVMR.SetParameterFloat.Bind(VBVMR)
+ if (p_name = "gain") {
+ p_value := VMRUtils.EnsureBetween(p_value, -60, this.GainLimit)
+ }
+ else if (p_name = "limit") {
+ p_value := VMRUtils.EnsureBetween(p_value, -60, 12.0)
+ }
+ else if (p_name = "mute") {
+ p_value := p_value < 0 ? !this.GetParameter("mute") : p_value
+ }
+ else if (InStr(p_name, "device")) {
+ local deviceParts := StrSplit(p_name, ".")
+ local deviceDriver := deviceParts.Length > 1 ? deviceParts[2] : "wdm"
+ local deviceName := p_value
+ ; Allow setting the device using a device object (e.g. bus.device := { name: "Headset", driver: "wdm" })
+ ; Device objects can be retrieved using VMR's GetBusDevice/GetStripDevice methods
+ if (IsObject(deviceName)) {
+ deviceDriver := deviceName.driver
+ deviceName := deviceName.name
+ }
+ if (!VMRAudioIO._IsValidDriver(deviceDriver))
+ throw VMRError(deviceDriver " is not a valid device driver", this.SetParameter.Name, p_name, p_value)
+ p_name := "device." deviceDriver
+ p_value := deviceName
+ }
+ return vmrFunc.Call(this.Id, p_name, p_value) == 0
+ }
+ /**
+ * 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 {
+ 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.
+ * - If the incremented value is not needed, 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, {@link @VMRAudioIO.GainLimit|`GainLimit`} doesn't apply here.
+ *
+ * @example usage
+ * vm.Bus[1].Increment("gain", 1) ; increases the gain by 1dB
+ * vm.Bus[1].Increment("gain", -5) ; decreases the gain by 5dB
+ *
+ * @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 {Boolean} - `true` if the parameter was incremented successfully.
+ */
+ Increment(p_param, p_amount) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return false
+ 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))
+ return VBVMR.SetParameters(script) == 0
+ }
+ /**
+ * 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 {Boolean} - `true` if the gain was set successfully.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ FadeTo(p_db, p_duration) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return false
+ if (!IsNumber(p_db))
+ throw VMRError("p_db must be a number", this.FadeTo.Name, p_db, p_duration)
+ if (!IsNumber(p_duration))
+ throw VMRError("p_duration must be a number", this.FadeTo.Name, p_db, p_duration)
+ return this.SetParameter("FadeTo", "(" p_db ", " p_duration ")")
+ }
+ /**
+ * 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 {Boolean} - `true` if the gain was set successfully.
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ FadeBy(p_dbAmount, p_duration) {
+ if (!VMRAudioIO.IS_CLASS_INIT)
+ return false
+ if (!IsNumber(p_dbAmount))
+ throw VMRError("p_dbAmount must be a number", this.FadeBy.Name, p_dbAmount, p_duration)
+ if (!IsNumber(p_duration))
+ throw VMRError("p_duration must be a number", this.FadeBy.Name, p_dbAmount, p_duration)
+ return this.SetParameter("FadeBy", "(" p_dbAmount ", " p_duration ")")
+ }
+ /**
+ * Returns `true` if the bus/strip is a physical (hardware) one.
+ * __________
+ * @returns {Boolean}
+ */
+ IsPhysical() => this._isPhysical
+ static _IsValidDriver(p_driver) => VMRUtils.IndexOf(VMRConsts.DEVICE_DRIVERS, p_driver) > 0
+ 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?) {
+ 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 ""
+ }
+}
+/**
+ * A wrapper class for voicemeeter buses.
+ * @extends {VMRAudioIO}
+ */
+class VMRBus extends VMRAudioIO {
+ static LEVELS_COUNT := 0
+ 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 := ""
+ /**
+ * Set/Get the bus's EQ parameters.
+ *
+ * @param {Number} p_channel - The zero-based index of the channel.
+ * @param {Number} p_cell - The zero-based index of the cell.
+ * @param {String} p_type - The EQ parameter to get/set.
+ *
+ * @example
+ * vm.Bus[1].EQ[1, 1, "gain"] := -6
+ * vm.Bus[1].EQ[1, 1, "q"] := 90
+ * __________
+ * @returns {Number} - The EQ parameter's value.
+ */
+ EQ[p_channel, p_cell, p_param] {
+ get => this.GetParameter("EQ.channel[" p_channel "].cell[" p_cell "]." p_param)
+ set => this.SetParameter("EQ.channel[" p_channel "].cell[" p_cell "]." p_param, Value)
+ }
+ /**
+ * 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)
+ }
+ }
+}
+/**
+ * A wrapper class for voicemeeter strips.
+ * @extends {VMRAudioIO}
+ */
+class VMRStrip extends VMRAudioIO {
+ static LEVELS_COUNT := 0
+ 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} p_name - The name of the application.
+ * @type {Number} - The application's gain (`0.0` to `1.0`).
+ * __________
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ AppGain[p_name] {
+ set {
+ VBVMR.SetParameter("AppGain", "(" p_name ", " Value ")")
+ }
+ }
+ /**
+ * Sets an application's mute state on the strip.
+ *
+ * @param {String} p_name - The name of the application.
+ * @type {Boolean} - The application's mute state.
+ * __________
+ * @throws {VMRError} - If invalid parameters are passed or if an internal error occurs.
+ */
+ AppMute[p_name] {
+ set {
+ VBVMR.SetParameter("AppGain", "(" p_name ", " Value ")")
+ }
+ }
+ /**
+ * 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][p_index + 1]
+ switch p_vmrType {
+ case 1:
+ super._isPhysical := this._index < 2
+ case 2:
+ super._isPhysical := this._index < 3
+ case 3:
+ super._isPhysical := this._index < 5
+ }
+ ; 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)
+ }
+ }
+}
+/**
+ * A wrapper class for Voicemeeter Remote that abstracts away 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 {Object} - An object containing information about the current Voicemeeter type.
+ * @see {@link VMRConsts.VOICEMEETER_TYPES|`VMRConsts.VOICEMEETER_TYPES`} for a list of available types.
+ */
+ Type := ""
+ /**
+ * 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()
+ /**
+ * 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.Type := VMRConsts.VOICEMEETER_TYPES[VBVMR.GetVoicemeeterType()].Clone()
+ 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 := VMRConsts.VOICEMEETER_TYPES[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 := VMRConsts.VOICEMEETER_TYPES.Length
+ loop vmTypeCount {
+ try {
+ vmPID := this.RunVoicemeeter((vmTypeCount + 1) - A_Index)
+ return vmPID
+ }
+ }
+ throw VMRError("Failed to launch Voicemeeter", this.RunVoicemeeter.Name)
+ }
+ /**
+ * Retrieves a strip (input) device by its name/driver.
+ * @param {String} p_name - The name of the device, or any substring of it.
+ * @param {String} p_driver - (Optional) The driver of the device, If omitted, {@link VMRConsts.DEFAULT_DEVICE_DRIVER|`VMRConsts.DEFAULT_DEVICE_DRIVER`} will be used.
+ * __________
+ * @returns {VMRDevice} The device object `{name, driver}`, or an empty string `""` if no device was found.
+ */
+ GetStripDevice(p_name, p_driver?) => VMRAudioIO._GetDevice(VMRStrip.Devices, p_name, p_driver?)
+ /**
+ * Retrieves all strip devices (input devices).
+ * __________
+ * @returns {Array} An array of {@link VMRDevice|`VMRDevice`} objects.
+ */
+ GetStripDevices() => VMRStrip.Devices
+ /**
+ * Retrieves a bus (output) device by its name/driver.
+ * @param {String} p_name - The name of the device, or any substring of it.
+ * @param {String} p_driver - (Optional) The driver of the device, If omitted, {@link VMRConsts.DEFAULT_DEVICE_DRIVER|`VMRConsts.DEFAULT_DEVICE_DRIVER`} will be used.
+ * __________
+ * @returns {VMRDevice} The device object `{name, driver}`, or an empty string `""` if no device was found.
+ */
+ GetBusDevice(p_name, p_driver?) => VMRAudioIO._GetDevice(VMRBus.Devices, p_name, p_driver?)
+ /**
+ * Retrieves all bus devices (output devices).
+ * __________
+ * @returns {Array} An array of {@link VMRDevice|`VMRDevice`} objects.
+ */
+ GetBusDevices() => VMRBus.Devices
+ /**
+ * 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)
+ }
+ 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
+ }
+ }
+ /**
+ * 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)
+ }
+ /**
+ * 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)
+ }
+ 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
+ }
+ /**
+ * @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)
+ }
+ /**
+ * @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))
+ }
+ ; TODO: Initialize macro buttons, recorder, vban, command, fx, option, patch
+ 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)
+ while (this.Sync()) {
+ }
+ ; Make sure all commands finish executing before logging out
+ Sleep(100)
+ VBVMR.Logout()
+ }
+}
diff --git a/examples/README.md b/examples/README.md
index 8edb763..252a48f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,8 +1,14 @@
## VMR.ahk Examples
+
* ### [Hotkey example](./hotkey_example.ahk): create hotkeys to control VoiceMeeter.
+
* ### [UI example](./ui_example.ahk): a simple GUI that controls VoiceMeeter's buses
- ![UI example](https://user-images.githubusercontent.com/47293197/118356850-d7be9e80-b57f-11eb-843a-db7f8dd996d1.gif)
+ ###### Not converted to v2 yet
+ ![UI example](https://user-images.githubusercontent.com/47293197/118356850-d7be9e80-b57f-11eb-843a-db7f8dd996d1.gif)
+
* ### [Level stabilizer](./level_stabilizer_example.ahk): stabilizes the audio level by adjusting the gain.
- ![Level stabilizer](https://user-images.githubusercontent.com/47293197/118352761-d2575900-b56b-11eb-98d0-9b4e43024249.gif)
+ ![Level stabilizer](https://user-images.githubusercontent.com/47293197/118352761-d2575900-b56b-11eb-98d0-9b4e43024249.gif)
+
* ### [Script example](./script_example.ahk): demonstrates two ways to write and execute a script.
+
* ### [MIDI example](./midi_message_example.ahk): demonstrates the usage of `onMidiMessage` callback.
diff --git a/examples/hotkey_example.ahk b/examples/hotkey_example.ahk
index 78daa2a..53505be 100644
--- a/examples/hotkey_example.ahk
+++ b/examples/hotkey_example.ahk
@@ -1,54 +1,77 @@
-#Include, %A_ScriptDir%\..\dist\VMR.ahk
+#Requires AutoHotkey >=2.0
-voicemeeter := new VMR().login()
-vol := 0.5
-voicemeeter.strip[6].AppGain := Format("(""Spotify"", {:.1f})", vol) ;set initial Spotify volume
-voicemeeter.bus[1].gain_limit:=0
+#Include %A_ScriptDir%\..\dist\VMR.ahk
-for i, bus in voicemeeter.bus {
- bus.gain:=0 ; set gain to 0 for all busses at startup
+voicemeeter := VMR().login()
+
+; Set the gain to 0 for all busses at startup
+for (bus in voicemeeter.Bus) {
+ bus.gain := 0
}
-Volume_Up::voicemeeter.bus[1].gain++ ;bind volume up key to increase bus[1] gain
-Volume_Down::voicemeeter.bus[1].gain--
+; jsdoc type annotations are not needed, but might allow your editor to offer relevant suggestions (see vscode-autohotkey2-lsp plugin)
+/** @type {VMRBus} */
+mainOutput := voicemeeter.Bus[1]
+mainOutput.GainLimit := 0
+
+/** @type {VMRStrip} */
+auxInput := voicemeeter.Strip[6]
-^M::voicemeeter.bus[1].mute-- ; bind ctrl+M to toggle mute bus[1]
+; Set initial Spotify volume
+spotifyVol := 0.5
+auxInput.AppGain["Spotify"] := spotifyVol
-^Volume_Up::ToolTip, % voicemeeter.strip[5].gain+=5
-^Volume_Down::ToolTip, % voicemeeter.strip[5].gain-=5
+; Bind volume keys to increase/decrease bus[1] gain
+Volume_Up:: mainOutput.gain++
+Volume_Down:: mainOutput.gain--
-F6::voicemeeter.bus[1].device:= "LG" ; set bus[1] to the first device with "LG" in its name using wdm driver
-F7::voicemeeter.strip[2].device["mme"]:= "amazonbasics"
+; Bind ctrl+M to toggle mute bus[1]
+^M:: mainOutput.mute := -1
-^G::
-MsgBox, % "bus[1] gain:" . voicemeeter.bus[1].gain . " dB"
-MsgBox, % "bus[1] gain percentage:" . voicemeeter.bus[1].getGainPercentage() . "%"
-MsgBox, % "bus[1] " . (voicemeeter.bus[1].mute ? "Muted" : "Unmuted")
-return
+^Volume_Up:: ToolTip(auxInput.gain += 5)
+^Volume_Down:: ToolTip(auxInput.gain -= 5)
-^Y::voicemeeter.command.show()
+F6:: mainOutput.device := voicemeeter.GetBusDevice("LG") ; Sets bus[1] to the first device with "LG" in its name using the default driver (wdm)
+F7:: voicemeeter.Strip[2].device := voicemeeter.GetStripDevice("amazonbasics", "mme")
+
+^G:: {
+ MsgBox(mainOutput.Name " gain:" . mainOutput.gain . " dB")
+ MsgBox(mainOutput.Name " gain percentage:" mainOutput.GainPercentage "%")
+ MsgBox(mainOutput.Name " " (mainOutput.mute ? "Muted" : "Unmuted"))
+}
-^K::voicemeeter.bus[1].FadeTo:="(-18.0, 2000)" ;set any parameter for a bus/strip
+; Not Supported yet
+; ^Y:: voicemeeter.Commands.Show()
-^T::MsgBox, % "Bus[1] Level: " . voicemeeter.bus[1].level[1]
+^K:: mainOutput.FadeTo(-18.0, 2000)
+; Or using a normal parameter setter:
+; ^K:: mainOutput.FadeTo := "(-18.0, 2000)"
-!r::
-voicemeeter.recorder.ArmStrip(4,1)
-voicemeeter.recorder["mode.Loop"]:=1
-voicemeeter.recorder.record:=1
-return
+^T:: MsgBox(mainOutput.Name " Level: " . mainOutput.Level[1])
-!s::
-voicemeeter.recorder.stop:=1
-voicemeeter.command.eject(1)
-return
+; Not Supported yet
+; !r:: {
+; voicemeeter.recorder.ArmStrip(4, 1)
+; voicemeeter.recorder["mode.Loop"] := 1
+; voicemeeter.recorder.record := 1
+; }
-^A::
-vol -= 0.1
-voicemeeter.strip[6].AppGain := Format("(""Spotify"", {:.1f})", vol) ;increase Spotify volume by 0.1
-return
+; Not Supported yet
+; !s:: {
+; voicemeeter.recorder.stop := 1
+; voicemeeter.command.eject(1)
+; }
-^D::
-vol += 0.1
-voicemeeter.strip[6].AppGain := Format("(""Spotify"", {:.1f})", vol) ;decrease Spotify volume by 0.1
-return
\ No newline at end of file
+; Decrease Spotify volume by 0.1
+^A:: {
+ global spotifyVol -= 0.1
+ auxInput.AppGain["Spotify"] := spotifyVol
+ ; Or using an index
+ ; auxInput.App[1, "Gain"] := spotifyVol
+}
+
+; Increase Spotify volume by 0.1
+^D:: {
+ global spotifyVol += 0.1
+ auxInput.AppGain["Spotify"] := spotifyVol
+}
diff --git a/examples/level_stabilizer_example.ahk b/examples/level_stabilizer_example.ahk
index 3a13fb1..ddde142 100644
--- a/examples/level_stabilizer_example.ahk
+++ b/examples/level_stabilizer_example.ahk
@@ -1,40 +1,52 @@
-#Include, %A_ScriptDir%\..\dist\VMR.ahk
-#Persistent
+#Requires AutoHotkey >=2.0
+#Include %A_ScriptDir%\..\dist\VMR.ahk
-; This is just an example to demo the `level` array
+Persistent(true)
+
+; This is just an example to demo the `Level` array
; It works best when the limits are set 20dBs apart
-global voicemeeter:= (new VMR).login()
-, device := voicemeeter.bus[1]
-, default_gain := device.gain
-, upper_limit := -20
-, lower_limit := -40
-voicemeeter.onUpdateLevels:= Func("levelStabilizer")
-OnExit("reset",-1)
-
-; stabilize the audio level by adjusting the gain
-levelStabilizer(){
- static is_stable
- lvl:= Max(device.level*) ; get the current peak level
- if(lvl = -999) ; if there's no sound
- device.gain:= default_gain
- else if(lvl >= upper_limit){ ; if the level is higher than the upper_limit
- device.FadeTo := "(" device.gain-5 ", 300)" ; lower the gain by 5dBs over 200 ms
- is_stable:=0
- }else if((device.gain < default_gain && !is_stable) || lvl <= lower_limit ){
- ; raise the gain if the level is lower than the lower_limit
- ; or if the level isn't stable and is lower than the default_limit
- device.FadeTo := "(" device.gain+1 ", 200)"
- if(lvl >= upper_limit)
- device.FadeTo := "(" device.gain-1 ", 200)"
- is_stable:=1
+voicemeeter := VMR().Login()
+
+/** @type {VMRBus} */
+device := voicemeeter.Bus[1]
+
+defaultGain := device.gain
+upperLimit := -20
+lowerLimit := -40
+
+voicemeeter.On(VMRConsts.Events.LevelsUpdated, levelStabilizer)
+OnExit(reset, -1)
+
+; stabilizes the audio level by adjusting the gain
+levelStabilizer() {
+ static isStable := false
+
+ ; get the current peak level
+ lvl := Max(device.Level*)
+ if (lvl = -999) { ; There's no sound
+ device.gain := defaultGain
+ }
+ else if (lvl >= upperLimit) { ; The level is higher than the upper limit
+ ; Lower the gain by 5dBs over 300 ms
+ device.FadeBy(-5, 300)
+ isStable := false
+ }
+ else if ((!isStable && device.gain < defaultGain) || lvl <= lowerLimit) {
+ ; Raise the gain if the level isn't stable and is lower than the default_limit
+ ; or if the level is lower than the lower_limit
+ device.FadeBy(1, 200)
+ if (lvl >= upperLimit)
+ device.FadeBy(-1, 200)
+ isStable := true
}
}
-reset(){
- device.gain:= default_gain ; reset to default gain
- Sleep, 100
+reset() {
+ ; Reset the bus's gain
+ device.gain := defaultGain
+ Sleep(100)
}
-*<^<+Q:: ;bind LCtrl + LShift + Q to exit
-ExitApp
\ No newline at end of file
+; Bind LCtrl + LShift + Q to exit
+*<^<+Q:: ExitApp()
\ No newline at end of file
diff --git a/examples/midi_message_example.ahk b/examples/midi_message_example.ahk
index 60db9e1..bd0a4e1 100644
--- a/examples/midi_message_example.ahk
+++ b/examples/midi_message_example.ahk
@@ -1,12 +1,13 @@
-#Include, %A_ScriptDir%\..\dist\VMR.ahk
-#Persistent
+#Requires AutoHotkey >=2.0
+#Include %A_ScriptDir%\..\dist\VMR.ahk
-global voicemeeter := new VMR().login()
-voicemeeter.onMidiMessage:= Func("writeMidi")
-return
+Persistent(true)
-; recieves an array of bytes that represents midi messages (every 3 elements represent a single midi message)
-; writes midi messages to a file (messages.txt)
-writeMidi(midi){
- FileAppend,% Format("Midi Message: {}, {}, {}`n",midi[1],midi[2],midi[3]), messages.txt
+voicemeeter := VMR().login()
+voicemeeter.On(VMRConsts.Events.MidiMessage, writeMidi)
+
+; Receives an array with the hex-formatted bytes of the message (every 3 elements represent a single midi message)
+; Writes midi messages to a file (messages.txt)
+writeMidi(midi) {
+ FileAppend(Format("Midi Message: {}, {}, {}`n", midi[1], midi[2], midi[3]), "messages.txt")
}
diff --git a/examples/script_example.ahk b/examples/script_example.ahk
index 9d89245..cc409ab 100644
--- a/examples/script_example.ahk
+++ b/examples/script_example.ahk
@@ -1,35 +1,39 @@
-#Include, %A_ScriptDir%\..\dist\VMR.ahk
-#Persistent
+#Requires AutoHotkey >=2.0
+#Include %A_ScriptDir%\..\dist\VMR.ahk
-global voicemeeter := (new VMR).login()
+Persistent(true)
+
+voicemeeter := VMR().login()
AhkScript()
-;VoiceMeeterScript()
-ExitApp
+VoiceMeeterScript()
+ExitApp()
AhkScript(){
+ global voicemeeter
; this is an AHK script
- ; indexes are one-based
- voicemeeter.strip[1].A1 := 1
- voicemeeter.strip[1].B1 := 0
- voicemeeter.bus[2].gain := -6.0
- voicemeeter.strip[3].gain := 12.0
- voicemeeter.recorder.A1 := 1
- voicemeeter.vban.outstream[4].name := "stream example"
+ ; indices are one-based
+ voicemeeter.Strip[1].A1 := true
+ voicemeeter.Strip[1].B1 := false
+ voicemeeter.Bus[2].gain := -6.0
+ voicemeeter.Strip[3].gain := 12.0
+ ; Not Supported yet
+ ; voicemeeter.recorder.A1 := 1
+ ; voicemeeter.vban.outstream[4].name := "stream example"
}
; OR
VoiceMeeterScript(){
- script =
+ local script := "
( LTrim Comments
; this is a voicemeeter script (not AHK)
- ; indexes are zero-based
+ ; indices are zero-based
Strip[0].A1 = 1
Strip[0].B1 = 0
Bus[1].gain = -6.0
Strip[2].gain = 12.0
Recorder.A1 = 1
vban.outstream[3].name = "stream example"
- )
- voicemeeter.exec(script)
+ )"
+ voicemeeter.Exec(script)
}
\ No newline at end of file
diff --git a/examples/ui_example.ahk b/examples/ui_example.ahk
deleted file mode 100644
index 40e9af4..0000000
--- a/examples/ui_example.ahk
+++ /dev/null
@@ -1,109 +0,0 @@
-#Include, %A_ScriptDir%\..\dist\VMR.ahk
-SetBatchLines, 20ms
-
-Global vm, GUI_hwnd, is_win_pos_changing:=0
-
-vm := new VMR().login()
-showUI()
-vm.onUpdateLevels:= Func("syncLevel") ; register level callback func
-vm.onUpdateParameters:= Func("syncParameters") ; register params callback func
-OnMessage(0x46, Func("onPosChanging"))
-
-showUI(){
- Global
- Gui, vm:New, +HwndGUI_hwnd, VoiceMeeter Remote UI
- xPos:=10
- Loop % vm.bus.Length() { ; add UI controls for each bus
- ;bus title
- yPos:=0, funcObj:=""
- Gui, Add, Text, x%xPos% y%yPos% w100, Bus[%A_Index%]
-
- ;bus level
- yPos+= 30
- Gui, Add, Progress, x%xPos% y%yPos% w20 h200 Range-72-20 c0x70C399 Background0x2C3D4D vertical Hwndbus_%A_Index%_level
-
- ;bus gain
- yPos+= 220
- Gui, Add, Edit, w50 x%xPos% y%yPos% ReadOnly
- Gui, Add, UpDown,Hwndbus_%A_Index%_gain Range-60-12 x0
- funcObj:= Func("updateParam").bind("gain", A_Index)
- GuiControl +g, % bus_%A_Index%_gain, % FuncObj
-
- ;bus mute
- yPos+= 30
- Gui, Add, CheckBox, x%xPos% y%yPos% Hwndbus_%A_Index%_mute, Mute
- funcObj:= Func("updateParam").bind("mute", A_Index)
- GuiControl +g, % bus_%A_Index%_mute, % FuncObj
-
- ;bus device
- if(vm.bus[A_Index].isPhysical()){ ; make sure the bus is a physical one (eg. 1-3 in banana)
- yPos+= 30
- Gui, Add, DropDownList, x%xPos% y%yPos% Hwndbus_%A_Index%_device
- funcObj:= Func("updateParam").bind("device", A_Index)
- GuiControl +g, % bus_%A_Index%_device, % FuncObj
- refreshDevices(A_Index)
- }
-
- xPos+=150
- }
- syncParameters() ; get initial values for gui controls
- Gui, Show, H350, VoiceMeeter Remote UI
-}
-
-; update vm bus parameters when they change on the AHK UI
-updateParam(param, index){
- GuiControlGet, val,,% bus_%index%_%param%
- if(param == "device"){
- RegExMatch(val, "iO)(?\w+): (?.+)", match)
- if(match)
- vm.bus[index].device[match.driver]:= match.name
- else
- vm.bus[index].device:= ""
- }else{
- vm.bus[index][param]:= val
- }
- SetTimer, syncParameters, -500 ; make sure params are in sync
-}
-
-; sync AHK UI controls with vm bus parameters
-syncParameters(){
- Loop % vm.bus.Length() {
- GuiControl,, % bus_%A_Index%_gain, % vm.bus[A_Index].gain
- GuiControl,, % bus_%A_Index%_mute, % Format("{:i}", vm.bus[A_Index].mute) ; convert 0.0/1.0 to 0/1
- if(vm.bus[A_Index].__isPhysical())
- refreshDevices(A_Index)
- }
-}
-
-; sync level meters with vm bus levels
-syncLevel(){
- if(is_win_pos_changing) ;dont update levels if the window is changing its position
- return
- Loop % vm.bus.Length() {
- GuiControl,, % bus_%A_Index%_level, % Max(vm.bus[A_Index].level*) ; get peak level for the bus
- }
-}
-
-; clears the bus's drop-down and reinserts the devices
-refreshDevices(index){
- elems:="| |" ; extra empty element for removing the device
- preSelected:= vm.bus[index].device ; get the pre-selected device
- for i, device in vm.getBusDevices() {
- elems.= Format("{:U}: {}", device.driver, device.name)
- elems.= device.name == preSelected? "||" : "|"
- }
- GuiControl,, % bus_%index%_device, % elems
-}
-
-vmGuiClose(){
- ExitApp
-}
-
-onPosChanging(){
- is_win_pos_changing:=1
- SetTimer, onPosChanged, -50
-}
-
-onPosChanged(){
- is_win_pos_changing:=0
-}