Controlling VoiceMeeter Banana with AHK Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
MinorKey
Posts: 16
Joined: 28 Mar 2016, 08:19

Controlling VoiceMeeter Banana with AHK

Post by MinorKey » 07 Jul 2017, 02:42

Hello,
I don't know if you're familiar with VoiceMeeter Banana. It's a virtual audio mixer, a kind of alternative to the standard volume mixer in Windows. I say a kind of, because it does far more than just simple volume control.
I want to write a custom user interface for it, but I don't want to rely on mouse coordinates and such. Luckily the program has its own API and I think that this should be possible. I quote:
"VoicemeeterRemote API is given by a simple standard DLL file for Windows to control Voicemeeter Audio Engine by another application, programmed in any language. It allows using voicemeeter functions and take advantage of Voicemeeter features in a client application. For example to make a more adapted user interface to a specific workflow, or to simply make a macro command to set different parameters at once, change the devices configuration and any kind of automation…"
So I guess that the building blocks are there but the problem is that I have no clue how to start putting them together. The program has an official development toolkit, I started reading it, but I almost got a headache, because I have no idea how to apply any of the things there to AutoHotkey. That being said, I don't think that it's that complicated - I believe that it's just my lack of knowledge and understanding.
I know that it's a lot to ask, but would someone here have the time to make a simple example for me how to retrieve and set a VoiceMeeter parameter from AHK? VoiceMeeter has its own possibilities to create macros, but for my needs these are insufficient. Naturally, I'd gladly pay you for the service, because I feel that I have reached my limit with this one.
The official toolkit can be found here:
http://vbaudio.jcedeveloppement.com/for ... ?f=8&t=346
I believe that the native VoiceMeeter scripting method only calls the VoicemeeterRemote API, so if you want to take a quick glance at what the commands do or look like, you can also take a look at the official user manual. That can be found here:
http://www.vb-audio.com/Voicemeeter/Voi ... Manual.pdf
The part on macros begins at page 26.
Now I don't even know how to close this post, because I realize that what I'm asking for goes far beyond the typical, but I'm really clueless how to go about this. I know that the AHK script will use DLL calls or COM objects (possibly both), but that's about it.
Thanks a million and have a nice day.
BoBo
Posts: 6564
Joined: 13 May 2014, 17:15

Re: Controlling VoiceMeeter Banana with AHK

Post by BoBo » 08 Jul 2017, 00:39

That's a really cool application, and definitely worth a look for all the folks out there. Especially the "ducking"-option is interesting for gamers and those who create (tutorial) videos. Hope to see some AHK Code coding gurus fiddeling with its Dll soon. Good luck and thx for sharing it :)

PS ... and that MIDI and macro stuff ... :thumbsup:
MinorKey
Posts: 16
Joined: 28 Mar 2016, 08:19

Re: Controlling VoiceMeeter Banana with AHK

Post by MinorKey » 08 Jul 2017, 01:09

Thanks for the reply, BoBo. :-)
It's the coolest app I've seen in a while. It even enables you to stream audio within your home network. The possibilities are almost endless + it's donationware so anyone can have the things it offers in their arsenal.
geekgarage
Posts: 22
Joined: 17 Dec 2017, 11:03
Contact:

Re: Controlling VoiceMeeter Banana with AHK

Post by geekgarage » 17 Dec 2017, 11:10

I don't know if this is still needed but basically to get data you need to work with pointers to memory locations. To get the memory address you call the VAR with '&' in front like this '&volLvlB0'

I've removed a lot of code for simplifications sake to explain how retrieving data works.

Code: Select all

global volLvlB0 = -35.0 ;setting 'volLvlB0' to -35.0 just to initialise in memory

...

Volume_Up:: ;react to volume up
	readVolLvl() ;call function
	volLvlB0 += 1 ;add 1 to volume variable for BUS0 (A1 OUT)
	...
	adjustVolLvl() ;Function called to adjust the volume
return


Volume_Down:: ;react to volume down
	readVolLvl() ;call function
	volLvlB0 -= 1 ;subtract 1 from volume variable for BUS0 (A1 OUT)
	...
	adjustVolLvl() ;Function called to adjust the volume
return

...

readVolLvl(){ ;function to read volume level from memory
	statusLvl0 := DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", "Bus[0].Gain", "Ptr", &volLvlB0, "Int") ;calls dll and set passed memory address to current gain/volume note we are passing the memory address as a pointer of volLvlB0 by adding & in front of it
	volLvlB0 := NumGet(&volLvlB0, Offset=0, Type = "Float")  ;get data from the memory address as float (according to VoicemeeterRemote.h it is a float)
}
Note that the memory address must be a part of the AHK and not the memory address of the value in the voicemeeter program

Also note that the above is a place to start as it is still not working for me. I can retrieve data but it's not correct, so i think my offset is incorrect and i can't figure out how to fix it, but if you msgbox the statusLvl0 variable it will show you 0 and that, according to voicemeeterremote.h file, is correct like it worked in setting the memory address to the current level :)
https://github.com/mirror/equalizerapo/ ... erRemote.h
geekgarage
Posts: 22
Joined: 17 Dec 2017, 11:03
Contact:

Re: Controlling VoiceMeeter Banana with AHK

Post by geekgarage » 17 Dec 2017, 19:08

in case any one knows how to fix this I would be very happy :) It doesn't retrieve the correct data and i have no clue why.

Full script

Code: Select all

;#NoTrayIcon
#Persistent
#SingleInstance force
#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
;#Warn  ; Recommended for catching common errors.
;#InstallKeybdHook ;https://autohotkey.com/docs/commands/_InstallKeybdHook.htm
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
#UseHook
ListLines Off
Process, Priority, , H
SetBatchLines, -1
SetKeyDelay, -1, -1
SetMouseDelay, -1
SetDefaultMouseSpeed, 0
SetWinDelay, -1
SetControlDelay, -1
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%
SetTitleMatchMode, 2
SetTitleMatchMode, Fast


OnExit("cleanup_before_exit")
global volLvlB0 = -35.0
global volLvlB1 = -35.0
global volLvlB2 = -35.0
global VMR_FUNCTIONS := {}
global VMR_DLL_DRIVE := "C:"
global VMR_DLL_DIRPATH := "Program Files (x86)\VB\Voicemeeter"
global VMR_DLL_FILENAME_32 := "VoicemeeterRemote.dll"
global VMR_DLL_FILENAME_64 := "VoicemeeterRemote64.dll"
global VMR_DLL_FULL_PATH := VMR_DLL_DRIVE . "\" . VMR_DLL_DIRPATH . "\"
Sleep, 500
if (A_Is64bitOS) {
    VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_64
} else {
    VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_32
}

; == START OF EXECUTION ==
; ========================

; Load the VoicemeeterRemote DLL:
; This returns a module handle
global VMR_MODULE := DllCall("LoadLibrary", "Str", VMR_DLL_FULL_PATH, "Ptr")
if (ErrorLevel || VMR_MODULE == 0)
    die("Attempt to load VoiceMeeter Remote DLL failed.")

; Populate VMR_FUNCTIONS
add_vmr_function("Login")
add_vmr_function("Logout")
add_vmr_function("RunVoicemeeter")
add_vmr_function("SetParameterFloat")
add_vmr_function("GetParameterFloat")
add_vmr_function("GetVoicemeeterVersion")

; "Login" to Voicemeeter, by calling the function in the DLL named 'VBVMR_Login()'...
login_result := DllCall(VMR_FUNCTIONS["Login"], "Int")
if (ErrorLevel || login_result < 0)
    die("VoiceMeeter Remote login failed.")

; If the login returns 1, that apparently means that Voicemeeter isn't running,
; so we start it; pass 1 to run Voicemeeter, or 2 for Voicemeeter Banana:
if (login_result == 1) {
    DllCall(VMR_FUNCTIONS["RunVoicemeeter"], "Int", 2, "Int")
    if (ErrorLevel)
        die("Attempt to run VoiceMeeter failed.")
    Sleep 2000
}

spoOpen := 0


; == HOTKEYS ==
; =============

loop{ ;used to change audio out mode when spotify opens
	WinGetTitle, spotifyname, ahk_class SpotifyMainWindow
	if(spotifyname != "" && spoOpen = 0){
		DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Bus[0].mode.Repeat", "Float", 1.0, "Int")
		spoOpen := 1
	} else if (spoOpen = 1 && spotifyname = ""){
		DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Bus[0].mode.Normal", "Float", 1.0, "Int")
		spoOpen := 0
	} else {
		;do nothing
	}
	Sleep, 5000
	ToolTip
}

Volume_Up::
	readVolLvl()
	volLvlB0 += 1
	volLvlB1 += 1
	volLvlB2 += 1
	adjustVolLvl()
return


Volume_Down::
	readVolLvl()
	volLvlB0 -= 1
	volLvlB1 -= 1
	volLvlB2 -= 1
	adjustVolLvl()
return

; == Functions ==
; ===============
;DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Strip[0].B1", "Float", 1.0, "Int")

readVolLvl(){
	DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", "Bus[0].Gain", "Ptr", &volLvlB0, "Int")
	DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", "Bus[1].Gain", "Ptr", &volLvlB1, "Int")
	DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", "Bus[2].Gain", "Ptr", &volLvlB2, "Int")
	volLvlB0 := NumGet(volLvlB0, "Float")
	volLvlB1 := NumGet(volLvlB1, "Float")
	volLvlB2 := NumGet(volLvlB2, "Float")
}

adjustVolLvl() {
	if (volLvlB0 > 12.0){
		volLvlB0 = 12.0
	} else if (volLvlB0 < -60.0) {
		volLvlB0 = -60.0
	}
	if (volLvlB1 > 12.0){
		volLvlB1 = 12.0
	} else if (volLvlB1 < -60.0) {
		volLvlB1 = -60.0
	}
	if (volLvlB2 > 12.0){
		volLvlB2 = 12.0
	} else if (volLvlB2 < -60.0) {
		volLvlB2 = -60.0
	}
	DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Bus[0].Gain", "Float", volLvlB0, "Int")
	DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Bus[1].Gain", "Float", volLvlB1, "Int")
	DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "Bus[2].Gain", "Float", volLvlB2, "Int")
}

add_vmr_function(func_name) {
    VMR_FUNCTIONS[func_name] := DllCall("GetProcAddress", "Ptr", VMR_MODULE, "AStr", "VBVMR_" . func_name, "Ptr")
    if (ErrorLevel || VMR_FUNCTIONS[func_name] == 0)
        die("Failed to register VMR function " . func_name . ".")
}

cleanup_before_exit(exit_reason, exit_code) {
    DllCall(VMR_FUNCTIONS["Logout"], "Int")
    ; OnExit functions must return 0 to allow the app to exit.
    return 0
}

die(die_string:="UNSPECIFIED FATAL ERROR.", exit_status:=254) {
    MsgBox 16, FATAL ERROR, %die_string%
    ExitApp exit_status
}

FHex( int, pad=8 ) { ; Function by [VxE]. Formats an integer (decimals are truncated) as hex.
; "Pad" may be the minimum number of digits that should appear on the right of the "0x".
	Static hx := "0123456789ABCDEF"
	If !( 0 < int |= 0 )
		Return !int ? "0x0" : "-" FHex( -int, pad )
	s := 1 + Floor( Ln( int ) / Ln( 16 ) )
	h := SubStr( "0x0000000000000000", 1, pad := pad < s ? s + 2 : pad < 16 ? pad + 2 : 18 )
	u := A_IsUnicode = 1
	Loop % s
		NumPut( *( &hx + ( ( int & 15 ) << u ) ), h, pad - A_Index << u, "UChar" ), int >>= 4
	Return h
}


Soscan2062
Posts: 2
Joined: 02 Jul 2015, 14:06
Contact:

Re: Controlling VoiceMeeter Banana with AHK  Topic is solved

Post by Soscan2062 » 25 Nov 2018, 13:18

I know this is almost a year later but I got it working (mostly) fine for me with the code below. I included the full code in case anyone else wants to give it a go. I couldn't find any other instances of it working for anyone else according to google. These were the two major changes that made it work.
-After reading the AHK docs for DllCall ( :o who'd of thought) I added NumGet before the DllCall and it started giving me the correct value.
-Per BNK3R Boy here I added isParametersDirty. For some reason the display can get off from what the number actually is and this seems to remedy it for the most part. If you're having issues I recommend to start debugging here.
I also made the GetParametersFloat function a bit more stand alone so you can use it to get almost any parameter.
On mine I set a hotkey to cycle through the audio sources and set the location in currentAudio.
If you're wanting to add a GUI and display a parameter from Voicemeeter I recommend separating the isParametersDirty and having it on its own thread per the Voicemeeter docs.

Code: Select all

[Codebox=autohotkey file=Untitled.ahk][/Codebox]
;~ #NoTrayIcon
#Persistent
#SingleInstance force
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn ; Recommended for catching common errors.
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
#UseHook
ListLines Off
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetTitleMatchMode RegEx
StringCaseSense Off 
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

OnExit("cleanup_before_exit")
SetFormat, Float, 0.3
global currentAudio := "Bus[0]"
global VMR_FUNCTIONS := {}
global VMR_DLL_DRIVE := "C:"
global VMR_DLL_DIRPATH := "Program Files (x86)\VB\Voicemeeter"
global VMR_DLL_FILENAME_32 := "VoicemeeterRemote.dll"
global VMR_DLL_FILENAME_64 := "VoicemeeterRemote64.dll"
global VMR_DLL_FULL_PATH := VMR_DLL_DRIVE . "\" . VMR_DLL_DIRPATH . "\"
Sleep, 500
if (A_Is64bitOS) {
VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_64
} else {
VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_32
}

; == START OF EXECUTION ==
; ========================

; Load the VoicemeeterRemote DLL:
; This returns a module handle
global VMR_MODULE := DllCall("LoadLibrary", "Str", VMR_DLL_FULL_PATH, "Ptr")
if (ErrorLevel || VMR_MODULE == 0)
die("Attempt to load VoiceMeeter Remote DLL failed.")

; Populate VMR_FUNCTIONS
add_vmr_function("Login")
add_vmr_function("Logout")
add_vmr_function("RunVoicemeeter")
add_vmr_function("SetParameterFloat")
add_vmr_function("GetParameterFloat")
add_vmr_function("IsParametersDirty")

; "Login" to Voicemeeter, by calling the function in the DLL named 'VBVMR_Login()'...
login_result := DllCall(VMR_FUNCTIONS["Login"], "Int")
if (ErrorLevel || login_result < 0)
die("VoiceMeeter Remote login failed.")

; If the login returns 1, that apparently means that Voicemeeter isn't running,
; so we start it; pass 1 to run Voicemeeter, or 2 for Voicemeeter Banana:
if (login_result == 1) {
DllCall(VMR_FUNCTIONS["RunVoicemeeter"], "Int", 2, "Int")
if (ErrorLevel)
die("Attempt to run VoiceMeeter failed.")
Sleep 2000
}

; == HOTKEYS ==
; =============

;add your code here

Media_Play_Pause::
if (currentAudio = "Bus[0]")
	currentAudio := "Bus[1]"
else if (currentAudio = "Bus[1]")
	currentAudio := "Strip[0]"
else if (currentAudio = "Strip[0]")
	currentAudio := "Strip[1]"
else
	currentAudio := "Bus[0]"
return

Volume_Up::
cLvl := readParam(currentAudio . ".Gain")
if (cLvl != ""){
	cLvl += 1
	adjustVolLvl(currentAudio . ".Gain", cLvl)
}
return

Volume_Down::
cLvl := readParam(currentAudio . ".Gain")
if (cLvl != ""){
	cLvl -= 1
	adjustVolLvl(currentAudio . ".Gain", cLvl)
}
return

Volume_Mute::
cM := readParam(currentAudio . ".Mute")
if (cM != "")
	adjustMute(currentAudio . ".Mute", cM)
return

; == Functions ==
; ===============
readParam(loc){
Loop
{
	pDirty := DLLCall(VMR_FUNCTIONS["IsParametersDirty"]) ;Check if parameters have changed. 
	if (pDirty==0) ;0 = no new paramters.
		break
	else if (pDirty<0) ;-1 = error, -2 = no server
		return ""
	else ;1 = New parameters -> update your display. (this only applies if YOU have a display, couldn't find any code to update VM display which can get off sometimes)
		if A_Index > 200
			return ""
		sleep, 20
}
tParamVal := 0.0
NumPut(0.0, tParamVal, 0, "Float")
statusLvl := DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", loc, "Ptr", &tParamVal, "Int")
tParamVal := NumGet(tParamVal, 0, "Float")
if (statusLvl < 0)
	return ""
else
	return tParamVal
}

adjustVolLvl(loc, tVol) {
if (tVol > 12.0)
	tVol = 12.0
else if (tVol < -60.0) 
	tVol = -60.0
DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tVol, "Int")
}

adjustMute(loc, tM) {
if (tM = 0)
	tM = 1
else
	tM = 0
DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tM, "Int")
}

add_vmr_function(func_name) {
VMR_FUNCTIONS[func_name] := DllCall("GetProcAddress", "Ptr", VMR_MODULE, "AStr", "VBVMR_" . func_name, "Ptr")
if (ErrorLevel || VMR_FUNCTIONS[func_name] == 0)
	die("Failed to register VMR function " . func_name . ".")
}

cleanup_before_exit(exit_reason, exit_code) {
DllCall(VMR_FUNCTIONS["Logout"], "Int")
; OnExit functions must return 0 to allow the app to exit.
return 0
}

die(die_string:="UNSPECIFIED FATAL ERROR.", exit_status:=254) {
MsgBox 16, FATAL ERROR, %die_string%
ExitApp exit_status
}
MinorKey
Posts: 16
Joined: 28 Mar 2016, 08:19

Re: Controlling VoiceMeeter Banana with AHK

Post by MinorKey » 25 Nov 2018, 14:13

Thank you, Soscan2062,
I haven't tested your code yet, but it looks good and so I've accepted your answer. I'll definitively give it a go at some point. Honestly, some of those things still look somewhat advanced to me, but I think that I'll manage to get it to work - especially with the comments you have included. :-)
BTW, a new version of VoiceMeeter called Potato will soon be released (perhaps it's out already). I hope that the developer hasn't changed the way the DLL works...
BNK3R Boy
Posts: 14
Joined: 18 Aug 2017, 05:55
Location: Germany
Contact:

Re: Controlling VoiceMeeter Banana with AHK

Post by BNK3R Boy » 18 Jan 2019, 09:45

Soscan2062 wrote:
25 Nov 2018, 13:18
I know this is almost a year later but I got it working (mostly) fine for me with the code below. I included the full code in case anyone else wants to give it a go. I couldn't find any other instances of it working for anyone else according to google. These were the two major changes that made it work.
-After reading the AHK docs for DllCall ( :o who'd of thought) I added NumGet before the DllCall and it started giving me the correct value.
-Per BNK3R Boy here I added isParametersDirty. For some reason the display can get off from what the number actually is and this seems to remedy it for the most part. If you're having issues I recommend to start debugging here.
I also made the GetParametersFloat function a bit more stand alone so you can use it to get almost any parameter.
On mine I set a hotkey to cycle through the audio sources and set the location in currentAudio.
If you're wanting to add a GUI and display a parameter from Voicemeeter I recommend separating the isParametersDirty and having it on its own thread per the Voicemeeter docs.

Code: Select all

[Codebox=autohotkey file=Untitled.ahk][/Codebox]
;~ #NoTrayIcon
#Persistent
#SingleInstance force
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn ; Recommended for catching common errors.
#MaxHotkeysPerInterval 99000000
#HotkeyInterval 99000000
#KeyHistory 0
#UseHook
ListLines Off
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetTitleMatchMode RegEx
StringCaseSense Off 
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

OnExit("cleanup_before_exit")
SetFormat, Float, 0.3
global currentAudio := "Bus[0]"
global VMR_FUNCTIONS := {}
global VMR_DLL_DRIVE := "C:"
global VMR_DLL_DIRPATH := "Program Files (x86)\VB\Voicemeeter"
global VMR_DLL_FILENAME_32 := "VoicemeeterRemote.dll"
global VMR_DLL_FILENAME_64 := "VoicemeeterRemote64.dll"
global VMR_DLL_FULL_PATH := VMR_DLL_DRIVE . "\" . VMR_DLL_DIRPATH . "\"
Sleep, 500
if (A_Is64bitOS) {
VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_64
} else {
VMR_DLL_FULL_PATH .= VMR_DLL_FILENAME_32
}

; == START OF EXECUTION ==
; ========================

; Load the VoicemeeterRemote DLL:
; This returns a module handle
global VMR_MODULE := DllCall("LoadLibrary", "Str", VMR_DLL_FULL_PATH, "Ptr")
if (ErrorLevel || VMR_MODULE == 0)
die("Attempt to load VoiceMeeter Remote DLL failed.")

; Populate VMR_FUNCTIONS
add_vmr_function("Login")
add_vmr_function("Logout")
add_vmr_function("RunVoicemeeter")
add_vmr_function("SetParameterFloat")
add_vmr_function("GetParameterFloat")
add_vmr_function("IsParametersDirty")

; "Login" to Voicemeeter, by calling the function in the DLL named 'VBVMR_Login()'...
login_result := DllCall(VMR_FUNCTIONS["Login"], "Int")
if (ErrorLevel || login_result < 0)
die("VoiceMeeter Remote login failed.")

; If the login returns 1, that apparently means that Voicemeeter isn't running,
; so we start it; pass 1 to run Voicemeeter, or 2 for Voicemeeter Banana:
if (login_result == 1) {
DllCall(VMR_FUNCTIONS["RunVoicemeeter"], "Int", 2, "Int")
if (ErrorLevel)
die("Attempt to run VoiceMeeter failed.")
Sleep 2000
}

; == HOTKEYS ==
; =============

;add your code here

Media_Play_Pause::
if (currentAudio = "Bus[0]")
	currentAudio := "Bus[1]"
else if (currentAudio = "Bus[1]")
	currentAudio := "Strip[0]"
else if (currentAudio = "Strip[0]")
	currentAudio := "Strip[1]"
else
	currentAudio := "Bus[0]"
return

Volume_Up::
cLvl := readParam(currentAudio . ".Gain")
if (cLvl != ""){
	cLvl += 1
	adjustVolLvl(currentAudio . ".Gain", cLvl)
}
return

Volume_Down::
cLvl := readParam(currentAudio . ".Gain")
if (cLvl != ""){
	cLvl -= 1
	adjustVolLvl(currentAudio . ".Gain", cLvl)
}
return

Volume_Mute::
cM := readParam(currentAudio . ".Mute")
if (cM != "")
	adjustMute(currentAudio . ".Mute", cM)
return

; == Functions ==
; ===============
readParam(loc){
Loop
{
	pDirty := DLLCall(VMR_FUNCTIONS["IsParametersDirty"]) ;Check if parameters have changed. 
	if (pDirty==0) ;0 = no new paramters.
		break
	else if (pDirty<0) ;-1 = error, -2 = no server
		return ""
	else ;1 = New parameters -> update your display. (this only applies if YOU have a display, couldn't find any code to update VM display which can get off sometimes)
		if A_Index > 200
			return ""
		sleep, 20
}
tParamVal := 0.0
NumPut(0.0, tParamVal, 0, "Float")
statusLvl := DllCall(VMR_FUNCTIONS["GetParameterFloat"], "AStr", loc, "Ptr", &tParamVal, "Int")
tParamVal := NumGet(tParamVal, 0, "Float")
if (statusLvl < 0)
	return ""
else
	return tParamVal
}

adjustVolLvl(loc, tVol) {
if (tVol > 12.0)
	tVol = 12.0
else if (tVol < -60.0) 
	tVol = -60.0
DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tVol, "Int")
}

adjustMute(loc, tM) {
if (tM = 0)
	tM = 1
else
	tM = 0
DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", loc, "Float", tM, "Int")
}

add_vmr_function(func_name) {
VMR_FUNCTIONS[func_name] := DllCall("GetProcAddress", "Ptr", VMR_MODULE, "AStr", "VBVMR_" . func_name, "Ptr")
if (ErrorLevel || VMR_FUNCTIONS[func_name] == 0)
	die("Failed to register VMR function " . func_name . ".")
}

cleanup_before_exit(exit_reason, exit_code) {
DllCall(VMR_FUNCTIONS["Logout"], "Int")
; OnExit functions must return 0 to allow the app to exit.
return 0
}

die(die_string:="UNSPECIFIED FATAL ERROR.", exit_status:=254) {
MsgBox 16, FATAL ERROR, %die_string%
ExitApp exit_status
}
I hope my 2 lines code was helpful. ;)

here some lines from another project to clear values.

Code: Select all

fncvmrv:=Func("clear_vmr_variable")
SetTimer, %fncvmrv%, 5

clear_vmr_variable() {
	DLLCall(VMR_FUNCTIONS["IsParametersDirty"])
}
...Dreh das Rad, Dreh das Rad, Dreh das Rad...
Ich dreh doch schon am Rad!
:think:
LightningSquirl
Posts: 1
Joined: 24 Oct 2020, 21:17

Re: Controlling VoiceMeeter Banana with AHK

Post by LightningSquirl » 24 Oct 2020, 21:45

Alright, so it's been 2 years since the last reply. I, along with probably many others, came across this thread while learning to control VoiceMeeter with AutoHotkey. I've learned quite a bit about the VoiceMeeterRemoteAPI, including the magic "Is Parameter Dirty".

Specifically, it's not some magical function that you call once. What it does is return a True / False boolean based on whether or not VoiceMeeter is ready to send you updated data. MEANING, you don't just call it and forget it.

Instead, you call it before trying to query anything from VoiceMeeter. For example, if you have a Volume Up / Down hotkey, but then go change the volume manually in the VoiceMeeter App, you should query the actual volume first, before applying any changes.

It turns out that VoiceMeeter takes some amount of time (more than 0 seconds) to actually change from Dirty to Not Dirty. So you cannot simply say "if not dirty, get volume". Instead, you must loop, repeatedly asking if Dirty, sleeping some amount of time, and asking again. Then break out or return once it's NOT Dirty to finally go Get the volume from VoiceMeeter.

Anyways, that understanding, as well as some other scripts I took inspiration from, has led me to build the following script. I've added a TON of comments, explaining what the hell is going on and why.

There's only a little bit of extra, as I like using CapsLock as my modifier so that it does not conflict with Ctrl and Alt when using Adobe Illustrator & Premiere, but also passes through to Games where I used it for PushToTalk. I also use Vim a lot, where I want CapsLock to be Esc temporarily when in Vim. I also have remapped my mouse keys to Numpad using Logitech and so my hotkeys show numpads. But that's all explained in the comments.

All of the startup is at the top. Utility functions in the middle, CapsLock stuff below, and Hotkey assignments and control logic at the bottom.

Read the script, read all the comments, and let me know here if it's of any help.

I spent a long time formatting. Hope it helps!

Code: Select all

;=========================================================================================
; Env Setup
;=========================================================================================


#NoEnv                        ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn                         ; Enable warnings to assist with detecting common errors.
SendMode Input                ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%   ; Ensures a consistent starting directory.
#SingleInstance Force         ; Determines whether a script is allowed to run again when it is already running. "Force" skips the dialog box and replaces the old instance automatically
#MaxHotkeysPerInterval 200    ; Allow more hotkeys in rapid succession... helps with the scroll wheel
DetectHiddenWindows, On       ; Allow detecting Windows that are minimized to Tray (such as Voicemeeterpro.exe)



;=========================================================================================
; VoiceMeeter Remote API Connection & Variable Initialization
;=========================================================================================


global voicemeeter_open := 0    ; Try to track the status of the VoiceMeeter Window
                                ; Might get out of sync if you manually Minimize or Show the VoiceMeeter window, so just hit Show/Hide hotkey toggle twice to fix

OpenVoicemeeter()       ; Call OpenVoicemeeter function to ensure Voicemeeter is running before the rest of the script executes
                        ; Will launch if closed, or bring to foreground if already running
                        ; Either way, the DllLoad should always work because Voicemeeter will be running

WinWait, ahk_exe voicemeeterpro.exe   ; Wait for voicemeeter
                                      ; Should be opening one way or another because of OpenVoicemeeter()

DllLoad := DllCall("LoadLibrary", "Str", "C:\Program Files (x86)\VB\Voicemeeter\VoicemeeterRemote64.dll")   ; Set this to your VoiceMeeter install directory


VMLogin()               ; Connect to VoiceMeeter

OnExit("VMLogout")      ; When script exists, disconnect from VoiceMeeter


; Set Initial State
ApplyVolume(0.0)        ; Output volume to 0.0   (VoiceMeeter's default, does NOT mean Mute)

UnMuteVolume()          ; Make sure it's not Muted

SetSpeakersOutput()     ; Select the Speakers as output, NOT Headphones, assuming you configured VoiceMeeter to have Speakers on A1 and Headphones on A2.  Change to however you like



;=========================================================================================
; Utility Functions
;=========================================================================================


VMLogin() {
    Login := DllCall("VoicemeeterRemote64\VBVMR_Login")
}


VMLogout() {
    Logout := DllCall("VoicemeeterRemote64\VBVMR_Logout")
}


OpenVoicemeeter() {
    IfWinExist ahk_exe voicemeeterpro.exe   ; If VoiceMeeter is already running, bring it up from the Tray and bring it to Foreground
    {
        WinShow ahk_exe voicemeeterpro.exe
        WinActivate ahk_exe voicemeeterpro.exe   ; Sometimes WinShow does not bring it in front of say, Spotify. So running WinActivate right after gives it focus and brings it all the way to the foreground
    }
    else   ; If VoiceMeeter is NOT running, run the .exe, wait for it to launch, then show and bring foreground
    {
        Run C:\Program Files (x86)\VB\Voicemeeter\voicemeeterpro.exe   ; Set this to your VoiceMeeter install directory
        WinWait ahk_exe voicemeeterpro.exe
        WinShow ahk_exe voicemeeterpro.exe
        WinActivate ahk_exe voicemeeterpro.exe   ; Sometimes WinShow does not bring it in front of say, Spotify. So running WinActivate right after gives it focus and brings it all the way to the foreground
    }
    voicemeeter_open := 1   ; Set state of VoiceMeeter for toggling between Open and Closed
}


HideVoicemeeter() {
    WinHide ahk_exe voicemeeterpro.exe   ; Hide VoiceMeeter back into the Tray.  This assumes you configured VoiceMeeter to "minimize to tray" rather than taskbar in it's settings
    voicemeeter_open := 0                ; Set state of VoiceMeeter for toggling between Open and Closed
}


WaitForNotDirty() {
    Loop
    {
        Dirty := DllCall("VoicemeeterRemote64\VBVMR_IsParametersDirty")   ; Check to see if VoiceMeeter says that parameters have changed elsewhere
        if (Dirty != 0)                                                   ; This would happen if you changed something in the app directly          
            sleep, 20                                                     ; It can stay dirty for a decent amount of time, in computer terms. Like a few hundred milliseconds
        else                                                              ; If it is still Dirty, wait a moment and check again
            return 1                                                      ; If it is NOT Dirty, Return 1 (True) 
    }
}


GetVolume() {
    WaitForNotDirty()                                                                                                     ; Make sure the VoiceMeeter parameters are not dirty before querying anything

    vm_volume := 0.0                                                                                                      ; Initialize variable
    NumPut(0.0, vm_volume, 0, "Float")                                                                                    ; Force it to be a float
                                                                                                                          ; The POINTER to the variable vm_volume is being sent to the Dll                                           
    Result := DllCall("VoicemeeterRemote64\VBVMR_GetParameterFloat", "AStr", "Bus[0].Gain", "Ptr", &vm_volume, "Int")     ; The "Result" is just a Success or Error Code                                                             
                                                                                                                          ; The actual "volume" of Speaker Channel (A1) is shoved into the memory address associated with the POINTER
    vm_volume := NumGet(vm_volume, 0, "Float")   ; Make sure the value of that variable is a Float after the Dll          ; Pointers are passed using the ' & ' before a variable                                                    
    vm_volume := Round(vm_volume)                ; Round the float so it's nicer to use later                             
    return vm_volume
}


ApplyVolume(vol_lvl) {
    if (vol_lvl > 12.0) {                ; If the volume is trying to go above 12.0, set it back to 12.0 as a Max
        vol_lvl := 12.0
    } else if (vol_lvl < -60.0) {        ; If the volume is trying to go below -60.0, set it back to -60.0 as the Min
        vol_lvl := -60.0
    }
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[0].Gain", "Float", vol_lvl)   ; Set the Speakers to vol_lvl
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[1].Gain", "Float", vol_lvl)   ; Set the Headphones to vol_lvl
}


GetIsMuted() {
    WaitForNotDirty()                                                                                                     ; Make sure the VoiceMeeter parameters are not dirty before querying anything

    is_muted := 0.0                                                                                                       ; Initialize variable
    NumPut(0.0, is_muted, 0, "Float")                                                                                     ; Force it to be a float
                                                                                                                          ; The POINTER to the variable is_muted is being sent to the Dll                                                 
    Result := DllCall("VoicemeeterRemote64\VBVMR_GetParameterFloat", "AStr", "Bus[0].Mute", "Ptr", &is_muted, "Int")      ; The "Result" is just a Success or Error Code                                                                  
                                                                                                                          ; The actual "muted state" of Speaker Channel (A1) is shoved into the memory address associated with the POINTER
    is_muted := NumGet(is_muted, 0, "Float")   ; Make sure the value of that variable is a Float after the Dll            ; Pointers are passed using the ' & ' before a variable                                                         
    return is_muted                            ; For some reason, not doing this makes the variable unusable              
}


MuteVolume() {
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[0].Mute", "Float", 1.0)   ; Sets Speaker Mute button to On
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[1].Mute", "Float", 1.0)   ; Sets Headphone Mute button to On
}


UnMuteVolume() {
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[0].Mute", "Float", 0.0)   ; Sets Speaker Mute button to Off
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Bus[1].Mute", "Float", 0.0)   ; Sets Headphone Mute button to Off
}


GetCurrentOutput() {
    WaitForNotDirty()                                                                                                     ; Make sure the VoiceMeeter parameters are not dirty before querying anything

    a1_active := 0.0                                                                                                      ; Initialize variable
    NumPut(0.0, a1_active, 0, "Float")                                                                                    ; Force it to be a float
                                                                                                                          ; The POINTER to the variable is_muted is being sent to the Dll                                                 
    Result := DllCall("VoicemeeterRemote64\VBVMR_GetParameterFloat", "AStr", "Strip[3].A1", "Ptr", &a1_active, "Int")     ; The "Result" is just a Success or Error Code                                                                  
                                                                                                                          ; The actual "muted state" of Speaker Channel (A1) is shoved into the memory address associated with the POINTER
    a1_active := NumGet(a1_active, 0, "Float")   ; Make sure the value of that variable is a Float after the Dll          ; Pointers are passed using the ' & ' before a variable                                                         
    return a1_active                             ; For some reason, not doing this makes the variable unusable              
}


SetSpeakersOutput() {
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Strip[3].A1", "Float", 1.0)   ; Sets Output Channel A1 to On (Speakers On)
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Strip[3].A2", "Float", 0.0)   ; Sets Output Channel A2 to Off (Headphones Off)
}


SetHeadphonesOutput() {
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Strip[3].A1", "Float", 0.0)   ; Sets Output Channel A1 to Off (Speakers Off)
    Result := DllCall("VoicemeeterRemote64\VBVMR_SetParameterFloat", "AStr", "Strip[3].A2", "Float", 1.0)   ; Sets Output Channel A2 to On (Headphones On)
}



;=========================================================================================
;  CapsLock Configuration
;=========================================================================================


SetCapsLockState, AlwaysOff     ; Keep CapsLock OFF, even when it is pressed and/or used in other hotkey combinations


~CapsLock::RAlt                 ; Reassign CapsLock to Right Alt for use in games
                                ; It's a key that doesn't conflict with other apps, but can still be detected in games as a separate key from normal Left Alt
                                ; Shows up as RAlt or sometimes Alt Gr

                                ; We also don't want this to be Esc all the time (such as for Vim), because Esc is not a mappable key in games
                                ; This is contextually switched to Esc in the next section


~CapsLock & Esc::               ; Override the override, explicitly toggle CapsLock On or back Off when CapsLock & Esc are pressed together
    GetKeyState, CapsLockState, CapsLock, T
    if CapsLockState = D
        SetCapsLockState, AlwaysOff
    else
        SetCapsLockState, AlwaysOn
return



;=========================================================================================
; Vim Context CapsLock -> Esc
;=========================================================================================


#IfWinActive ahk_class Vim      ; When Vim window is active

~CapsLock::Esc                  ; Map CapsLock to Esc

                                ; Tilde means the CapsLock is still passed through to Windows, but it is eaten by "SetCapsLockState, Always Off"
                                ; More importantly, it also makes the hotkey trigger immediately, rather than on "key UP", which means that Esc fires the moment CapsLock is pressed DOWN

#IfWinActive                    ; Snap out of Vim window scope stuff, go back to normal scope for the rest of the script



;=========================================================================================
; Volume VoiceMeeter Control
;=========================================================================================


; Numpads are used in some places because that's what I've bound my mouse buttons to using Logitech software


; Volume Up
~CapsLock & WheelUp::                 ; Get volume from VoiceMeeter, in case it's been changed directly
    ApplyVolume(GetVolume() + 2.0)    ; Increase the volume in steps of 2, more reasonable when using scroll wheel
return


; Volume Down
~CapsLock & WheelDown::
    ApplyVolume(GetVolume() - 2.0)    ; Same, but decrease volume
return
 

; Return Volume to Default 0.0
~CapsLock & Numpad0::                 ; Numpad0 is the "Thumb Sniper Button" on my Logitech G502
    ApplyVolume(0.0)                  ; 0.0 is the "default" for VoiceMeeter, so clicking this gets us back without having to look
return


; Volume Mute
~CapsLock & MButton::                 ; MButton is "Middle Mouse Button", aka click the scroll wheel down
    if (GetIsMuted() = 0.0) {
        MuteVolume()
    } else {
        UnMuteVolume()
    }
return


; Switch Audio Devices
~CapsLock & Up::                      ; Up is talking about keyboard Arrow Keys
    if (GetCurrentOutput() = 1) {
        SetHeadphonesOutput()
    } else {
        SetSpeakersOutput()
    }
return


; Open VoiceMeeter Banana
~CapsLock & Numpad7::                 ; Numpad7 is the little button up near the scroll wheel click/freespin toggle.  Labeled G9 on Logitech G502
    if (voicemeeter_open = 0) {       ; Control variable gets toggled to easily decide whether to Open or Hide when re-clicking the same hotkey repeatedly
        OpenVoicemeeter()             ; Runs new or shows existing VoiceMeeter
    } else {
        HideVoiceMeeter()             ; Hides running VoicemMeeter to Tray
    }
return



;=========================================================================================
; Media Controls
;=========================================================================================


; Numpads are used in some places because that's what I've bound my mouse buttons to using Logitech software


~CapsLock & Right::                   ; Right is talking about keyboard Arrow Keys
    Send {Media_Next}
return


~CapsLock & Left::                    ; Left is talking about keyboard Arrow Keys
    Send {Media_Prev}
return


~CapsLock & Down::                    ; Down is talking about keyboard Arrow Keys
    Send {Media_Play_Pause}
return


~CapsLock & Numpad4::                 ; Numpad4 is click Mouse Wheel Right on my Logitech G502
    Send {Media_Next}
return


~CapsLock & Numpad3::                 ; Numpad3 is click Mouse Wheel Left on my Logitech G502
    Send {Media_Prev}
return



;=========================================================================================
; Open with Vim
;=========================================================================================


~CapsLock & Enter::                                                     ; Initialize the built-in Clipboard variable
    Clipboard =                                                         ; Assumes you have single-click selected a file in Explorer 
    Send ^c                                                             ; Literally sends "Control C" to Windows, which fires "Copy"
    ClipWait, 1                                                         ; Wait for the Clipboard to be filled with something, only wait 1 second, in case something screws up
    Run, C:\Program Files (x86)\Vim\vim81\gvim.exe `"%clipboard%`"      ; Run gvim.exe passing along the copied filename, escaping the " using ` so that the run command literally contains quotes, which fixes filepaths with spaces and special characters

    SetKeyDelay,-1              ; When this fires, the context switches to VIM, so CapsLock becomes "Esc"
    Send,{Blind}{RAlt Up}       ; Therefore "RAlt Up" is never sent, meaing that "RAlt" get stuck "Down"                      
return                          ; These two lines simulate what usually happens when you let go of "~CapsLock::Ralt"  


fat_flying_pigs
Posts: 1
Joined: 13 Jun 2021, 03:40

Re: Controlling VoiceMeeter Banana with AHK

Post by fat_flying_pigs » 13 Jun 2021, 03:43

Any idea how to make the `recorder.load` work? The below returns a value of -1 (which appears to be a generic VB error)

Code: Select all

DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", "recorder.load", "AStr", "F:\sounds\alarm.mp3", "Int")
onyx_online
Posts: 1
Joined: 14 Jul 2021, 19:13

Re: Controlling VoiceMeeter Banana with AHK

Post by onyx_online » 14 Jul 2021, 19:19

This wrapper provides access to the Recorder.

https://github.com/SaifAqqad/VMR.ahk
ellisb
Posts: 1
Joined: 06 Sep 2023, 14:44

Re: Controlling VoiceMeeter Banana with AHK

Post by ellisb » 06 Sep 2023, 15:35

Since this thread seems to get revived every 2 years, I'll do it again.

All i need it is AHK to send a keyboard shortcut (CTL ALT B) when input A1 is unmuted, and another when muted (CTL ATL Y). This would allow me to automate my "on air" status light.
Juan0952
Posts: 2
Joined: 17 Nov 2023, 05:46

Re: Controlling VoiceMeeter Banana with AHK

Post by Juan0952 » 17 Nov 2023, 06:05

Just something I was working on, it works, not good enough since it has some milliseconds of delay but it works. I used the library from https://github.com/onyx-and-iris/voicemeeter-api-powershell (you need to install it manually on your PowerShell by pasting Install-Module -Name Voicemeeter -Scope CurrentUser). From there you can send things to voicemeeter through PowerShell:

Code: Select all

$vmr = Get-RemoteBanana
$vmr.strip[0].mute=1
# [... other commands, some logic and after you are done:]
$vmr.Logout()
From that, I created the code bellow, it will simply open powershell if not open and send the desired command through a function. This example will mute strip[0] with ctrl + ç, all commands can be found on the voicemeeter documentation.

Code: Select all

#InstallKeybdHook
#InstallMouseHook
#Persistent
#NoEnv
#SingleInstance Force
SetTitleMatchMode 2 ; allow partial titles
SetKeyDelay, -1,1 ; faster key strokes


; make sure you went to powershell and pasted the following command to install the library from https://github.com/onyx-and-iris/voicemeeter-api-powershell
; Install-Module -Name Voicemeeter -Scope CurrentUser
WriteMessageToPowerShell(instance,message) {
  IfWinExist VoiceMeeterPowershell
  {
    ; If PowerShell is already open, send the PowerShell code to the existing window named VoiceMeeterPowershell
		ControlSendRaw,,if (!(Test-Path variable:\vmr) ) {$vmr = %instance%}`n,VoiceMeeterPowershell ; check if the instance exists
		ControlSend,,$vmr.%message%`n,VoiceMeeterPowershell ; send the command
  }
  else
  {
    ; If PowerShell is not open, start a new process in Minimized mode, name it and send the PowerShell code
    Run, powershell.exe -NoExit -WindowStyle Minimized -Command $host.UI.RawUI.WindowTitle = 'VoiceMeeterPowershell'`n,,;Hide
		WinWait,VoiceMeeterPowershell,, 10 ; wait up to 10 seconds to powershell to be ready
    ControlSend,,$vmr = %instance%`n,VoiceMeeterPowershell ; declare the instance
		ControlSend,,$vmr.%message%`n,VoiceMeeterPowershell ; send the command
  }
}

^ç::
  ; Example of calling the function with the desired command
  WriteMessageToPowerShell("Get-RemoteBanana","strip[0].mute=1")
  ; Im kinda lazy to do it, but here you can call some audio feedback
return
Juan0952
Posts: 2
Joined: 17 Nov 2023, 05:46

Re: Controlling VoiceMeeter Banana with AHK

Post by Juan0952 » 17 Nov 2023, 16:58

Second upgrade:

Code: Select all

#InstallKeybdHook
#InstallMouseHook
#Persistent
#NoEnv
#SingleInstance Force
SetTitleMatchMode 2 ; allow partial titles
SetKeyDelay, -1,1 ; faster key strokes


IsNumberDefined(myNumber) {
    global myNumbers
    for index, value in myNumbers
    {
        if (value == myNumber)
            return true
    }
    return false
}

; make sure you went to powershell and pasted the following command to install the library from https://github.com/onyx-and-iris/voicemeeter-api-powershell
; Install-Module -Name Voicemeeter -Scope CurrentUser
myNumbers := []  ; Initialize an empty array to store defined numbers
WriteMessageToPowerShell(instance,shortcut,isToggle, message) {
    IfWinExist VoiceMeeterPowershell
    {
        if (!IsNumberDefined(shortcut))
        {
            ; If PowerShell is already open, send the PowerShell code to the existing window named VoiceMeeterPowershell
            sendToPowerShell(isToggle,shortcut,message)
        }
        ControlSend,,&$%shortcut%`n,VoiceMeeterPowershell ; send the command
    }
    else
    {
        ; If PowerShell is not open, start a new process in hidden mode, name it, and send the PowerShell code
        Run, powershell.exe -NoExit -WindowStyle Minimized -Command $host.UI.RawUI.WindowTitle = 'VoiceMeeterPowershell'`n,,;Hide
        WinWait, VoiceMeeterPowershell,, 10 ; wait up to 10 seconds for PowerShell to be ready
        ControlSend,,$vmr = %instance%`n,VoiceMeeterPowershell ; declare the instance
        sendToPowerShell(isToggle,shortcut,message)
        ControlSend,,&$%shortcut%`n,VoiceMeeterPowershell ; send the command
    }
}

SendToPowerShell(isToggle,shortcut,message){
    global myNumbers
    if (isToggle == "toggle")
    {
        ControlSendRaw,,$%shortcut% = { echo $vmr.%message%
        ControlSend,,{VKBF}
        ControlSendRaw,,$vmr.%message% = -not $vmr.%message% }`n,VoiceMeeterPowershell ; create a shortcut of the command
    }
    Else
    {
        ControlSendRaw,,$%shortcut% = { echo $vmr.%message%
        ControlSend,,{VKBF}
        ControlSendRaw,,$vmr.%message% }`n,VoiceMeeterPowershell ; create a shortcut of the command
    }
    myNumbers.Push(shortcut)
}

^ç::
    ; Example of calling the function with the desired command
    ; first parameter is the Voicemeeter you are using
    ; second parameter is the shortcut you want, DO NOT REPEAT THEM, use just one character for more speed
    ; third parameter is if it's toggle, it will get the value and invert it
    ; forth parameter is the command, if toggle you must ocult the assignment
    WriteMessageToPowerShell("Get-RemoteBanana","0","none", "strip[1].mute=1")
    WriteMessageToPowerShell("Get-RemoteBanana","1","toggle", "strip[0].mute")
return
Post Reply

Return to “Ask for Help (v1)”