#Include, flag.ahk SetWorkingDir, C:\Users\Leand.000\Documents\D3K #SingleInstance force #MaxHotkeysPerInterval 99000000 #HotkeyInterval 99000000 #KeyHistory 0 #UseHook #Persistent ListLines Off SendMode Input ; Recommended for new scripts due to its superior speed and reliability. SetTitleMatchMode, RegEx StringCaseSense Off OnExit("cleanup_before_exit") SetFormat, Float, 0.3 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 } ; == MIDI == ; ========== #Include, MIDI\MidiStart.ahk #Include, MIDI\Midi_In_and_GuiMonitor.ahk ; this file contains: the function to parse midi message into parts we can work with and a midi monitor. #Include, MIDI\MidiRules.ahk ; this file contains: Rules for manipulating midi input then sending modified midi output. #Include, MIDI\Midi_under_the_hood.ahk ; this file contains: (DO NOT EDIT THIS FILE) all the dialogs to set up midi ports and midi message handling. midiCCin: cc := byte1 ; The Control Channel val := byte2 ; The value (0-127 for faders, 0 or 1 for buttons (that part is set with the software)) Switch chan { Case 1: ; The first fader: Work Laptop Receive Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[0].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[0].Pan_x", Lvl) Case 2: adjustToggle("Strip[0].Solo", val) Return Case 3: adjustToggle("Strip[0].Mute", val) Return Case 4: adjustToggle("Strip[0].B2", val) Return Default: Return } Case 2: ; The second fader: Work Laptop Send Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[2].Gain", Lvl) Return Case 2, 3, 4: adjustToggle("Bus[2].Mute", val) Return Default: Return } Case 3: ; The third fader: Desktop Audio Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[3].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[3].Pan_x", Lvl) Case 2: adjustToggle("Strip[3].Solo", val) Return Case 3: adjustToggle("Strip[3].Mute", val) Return Case 4: adjustToggle("Strip[3].B2", val) Return Default: Return } Case 4: ; The third fader: Comms Send Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[3].Gain", Lvl) Return Case 2: ; PTT if !(val){ cSendMute := Round(readParam("Bus[3].Mute")) adjustToggle("Bus[3].Mute", False) } else { adjustToggle("Bus[3].Mute", cSendMute) } Return Case 3: adjustToggle("Bus[3].Mute", val) Return Case 4: ; Push to mute if !(val){ cSendMute := Round(readParam("Bus[3].Mute")) adjustToggle("Bus[3].Mute", True) } else { adjustToggle("Bus[3].Mute", cSendMute) } Return Default: Return } Case 5: ; The fifth fader: Comms Receive Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[4].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[4].Pan_x", Lvl) Case 2: adjustToggle("Strip[4].Solo", val) Return Case 3: adjustToggle("Strip[4].Mute", val) Return Case 4: adjustToggle("Strip[4].B2", val) Return Default: Return } Case 6: ; The sixth fader: Speakers Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[0].Gain", Lvl) Return Case 3: adjustToggle("Bus[0].Mute", val) Return Default: Return } Case 7: ; The seventh fader: Mic Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[1].Gain", Lvl) Return Case 3: adjustToggle("Strip[1].Mute", val) adjustToggle("Bus[1].Mute", val) Return Case 4: adjustToggle("Strip[1].B2", val) Return Default: Return } Case 8: ; The eighth fader: Music Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[2].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[2].Pan_x", Lvl) Case 2: adjustToggle("Strip[2].Solo", val) Return Case 3: adjustToggle("Strip[2].Mute", val) Return Case 4: ; Send audio to Comms out & record adjustToggle("Strip[2].B1", val) adjustToggle("Strip[2].B2", val) if (val) { Send {F22} ; Toggle PTT/Voice Activity (Shows up as UNK133 in discord) Send {F23 down} ; PTT button (Shows up as UNK134 in discord) } else { Send {F23 up} Send {F22} ; Toggle PTT/Voice Activity } Return Default: Return } Case 10: ; VoiceMeeter recorder controls Switch cc { Case 1: ; Media Previous if (val) { Send, {Media_Prev} } Return Case 2: ; Media Next if (val) { Send, {Media_Next} } Return Case 3: ; Media Play/Pause If (WinActive("Netflix|YouTube",, "Music")) { Send, {Space} } Else { Send, {Media_Play_Pause} } Return Case 4: ; Set button / death sound if (val) { Send {F22} ; Toggle PTT/Voice Activity Send {F23 down} adjustToggle("Recorder.Play", True) ; Temp fix until file loading works } else { Send {F23 up} Send {F22} adjustToggle("Recorder.Stop", True) Sleep, 250 ; Makes sure the playback is reset to the beginning, temp until loading files is possible adjustToggle("Recorder.Stop", True) } Return Case 5: ; <- under Marker / dislike song if !(val) { Send {F20} } Return Case 6: ; -> under Marker / like song if !(val) { Send {F21} } Return Case 7: ; Rewind if (val) { adjustToggle("Recorder.REW", True) } else { adjustToggle("Recorder.Play", True) } Return Case 8: ; Fast Forward if (val) { adjustToggle("Recorder.FF", True) } else { adjustToggle("Recorder.Play", True) } Return Case 9: adjustToggle("Recorder.Stop", val) Return Case 10: adjustToggle("Recorder.Play", val) Return Case 11: adjustToggle("Recorder.Record", val) Return Default: Gosub, SendCC Return } Default: Gosub, SendCC Return } Return ; == HOTKEYS == ; ============= Volume_Mute:: b0M := Round(readParam("Bus[0].Mute")) ; Speakers b1M := Round(readParam("Bus[1].Mute")) ; Headphones b2M := Round(readParam("Bus[2].Mute")) ; Work Laptop Send b3M := Round(readParam("Bus[3].Mute")) ; Comms Send b4M := Round(readParam("Bus[4].Mute")) ; Recording cM := b0M + b1M + b2M + b3M + b4M if (cM = "5") { ; Unmute the ones that were unmuted before adjustToggle("Bus[0].Mute", b0Ms) ; Speakers adjustToggle("Bus[1].Mute", b1Ms) ; Headphones adjustToggle("Bus[2].Mute", b2Ms) ; Work Laptop Send adjustToggle("Bus[3].Mute", b3Ms) ; Comms Send adjustToggle("Bus[4].Mute", b4Ms) ; Recording Run %A_AhkPath% "flag.ahk" "off" } else { adjustToggle("Bus[0].Mute", True) ; Speakers adjustToggle("Bus[1].Mute", True) ; Headphones adjustToggle("Bus[2].Mute", True) ; Work Laptop Send adjustToggle("Bus[3].Mute", True) ; Comms Send adjustToggle("Bus[4].Mute", True) ; Recording b0Ms := b0M b1Ms := b1M b2Ms := b2M b3Ms := b3M b4Ms := b4M Run %A_AhkPath% "flag.ahk" "s" "red" } Return Volume_Up:: cM := Round(readParam("Strip[3].Mute")) if !(cM) { cLvl := readParam("Strip[3].Gain") if (cLvl != "") { cLvl += 1 adjustVolLvl("Strip[3].Gain", cLvl) } } Return Volume_Down:: cM := Round(readParam("Strip[3].Mute")) if !(cM) { cLvl := readParam("Strip[3].Gain") if (cLvl != "") { cLvl -= 1 adjustVolLvl("Strip[3].Gain", cLvl) } } 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") } adjustToggle(func, togg) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", func, "Float", togg, "Int") } adjustString(func, str) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", func, "AStr", str, "Str") } fader_to_fader(val) ; Translates MIDI fader values to the VoiceMeeter software faders { ; The upper limit for faders in VM is 12.0 ; The lower limit for the faders in VM that I would like is -40.0, how low it should go when the physical fader is all the way at the bottom nval := Round(((val / 127) * 72) - 60) Return nval } ; Formula: ((value / max value) * total range) - negative part of range dial_to_pan(val) { nval := Round((val / 127) - 0.5, 2) Return nval } 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 }#Include, flag.ahk SetWorkingDir, C:\Users\Leand.000\Documents\D3K #SingleInstance force #MaxHotkeysPerInterval 99000000 #HotkeyInterval 99000000 #KeyHistory 0 #UseHook #Persistent ListLines Off SendMode Input ; Recommended for new scripts due to its superior speed and reliability. SetTitleMatchMode, RegEx StringCaseSense Off OnExit("cleanup_before_exit") SetFormat, Float, 0.3 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 } ; == MIDI == ; ========== #Include, MIDI\MidiStart.ahk #Include, MIDI\Midi_In_and_GuiMonitor.ahk ; this file contains: the function to parse midi message into parts we can work with and a midi monitor. #Include, MIDI\MidiRules.ahk ; this file contains: Rules for manipulating midi input then sending modified midi output. #Include, MIDI\Midi_under_the_hood.ahk ; this file contains: (DO NOT EDIT THIS FILE) all the dialogs to set up midi ports and midi message handling. midiCCin: cc := byte1 ; The Control Channel val := byte2 ; The value (0-127 for faders, 0 or 1 for buttons (that part is set with the software)) Switch chan { Case 1: ; The first fader: Work Laptop Receive Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[0].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[0].Pan_x", Lvl) Case 2: adjustToggle("Strip[0].Solo", val) Return Case 3: adjustToggle("Strip[0].Mute", val) Return Case 4: adjustToggle("Strip[0].B2", val) Return Default: Return } Case 2: ; The second fader: Work Laptop Send Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[2].Gain", Lvl) Return Case 2, 3, 4: adjustToggle("Bus[2].Mute", val) Return Default: Return } Case 3: ; The third fader: Desktop Audio Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[3].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[3].Pan_x", Lvl) Case 2: adjustToggle("Strip[3].Solo", val) Return Case 3: adjustToggle("Strip[3].Mute", val) Return Case 4: adjustToggle("Strip[3].B2", val) Return Default: Return } Case 4: ; The third fader: Comms Send Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[3].Gain", Lvl) Return Case 2: ; PTT if !(val){ cSendMute := Round(readParam("Bus[3].Mute")) adjustToggle("Bus[3].Mute", False) } else { adjustToggle("Bus[3].Mute", cSendMute) } Return Case 3: adjustToggle("Bus[3].Mute", val) Return Case 4: ; Push to mute if !(val){ cSendMute := Round(readParam("Bus[3].Mute")) adjustToggle("Bus[3].Mute", True) } else { adjustToggle("Bus[3].Mute", cSendMute) } Return Default: Return } Case 5: ; The fifth fader: Comms Receive Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[4].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[4].Pan_x", Lvl) Case 2: adjustToggle("Strip[4].Solo", val) Return Case 3: adjustToggle("Strip[4].Mute", val) Return Case 4: adjustToggle("Strip[4].B2", val) Return Default: Return } Case 6: ; The sixth fader: Speakers Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[0].Gain", Lvl) Return Case 3: adjustToggle("Bus[0].Mute", val) Return Default: Return } Case 7: ; The seventh fader: Mic Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Bus[1].Gain", Lvl) Return Case 3: adjustToggle("Strip[1].Mute", val) adjustToggle("Bus[1].Mute", val) Return Case 4: adjustToggle("Strip[1].B2", val) Return Default: Return } Case 8: ; The eighth fader: Music Switch cc { Case 0: Lvl := fader_to_fader(val) adjustVolLvl("Strip[2].Gain", Lvl) Return Case 1: Lvl := dial_to_pan(val) adjustVolLvl("Strip[2].Pan_x", Lvl) Case 2: adjustToggle("Strip[2].Solo", val) Return Case 3: adjustToggle("Strip[2].Mute", val) Return Case 4: ; Send audio to Comms out & record adjustToggle("Strip[2].B1", val) adjustToggle("Strip[2].B2", val) if (val) { Send {F22} ; Toggle PTT/Voice Activity (Shows up as UNK133 in discord) Send {F23 down} ; PTT button (Shows up as UNK134 in discord) } else { Send {F23 up} Send {F22} ; Toggle PTT/Voice Activity } Return Default: Return } Case 10: ; VoiceMeeter recorder controls Switch cc { Case 1: ; Media Previous if (val) { Send, {Media_Prev} } Return Case 2: ; Media Next if (val) { Send, {Media_Next} } Return Case 3: ; Media Play/Pause If (WinActive("Netflix|YouTube",, "Music")) { Send, {Space} } Else { Send, {Media_Play_Pause} } Return Case 4: ; Set button / death sound if (val) { Send {F22} ; Toggle PTT/Voice Activity Send {F23 down} adjustToggle("Recorder.Play", True) ; Temp fix until file loading works } else { Send {F23 up} Send {F22} adjustToggle("Recorder.Stop", True) Sleep, 250 ; Makes sure the playback is reset to the beginning, temp until loading files is possible adjustToggle("Recorder.Stop", True) } Return Case 5: ; <- under Marker / dislike song if !(val) { Send {F20} } Return Case 6: ; -> under Marker / like song if !(val) { Send {F21} } Return Case 7: ; Rewind if (val) { adjustToggle("Recorder.REW", True) } else { adjustToggle("Recorder.Play", True) } Return Case 8: ; Fast Forward if (val) { adjustToggle("Recorder.FF", True) } else { adjustToggle("Recorder.Play", True) } Return Case 9: adjustToggle("Recorder.Stop", val) Return Case 10: adjustToggle("Recorder.Play", val) Return Case 11: adjustToggle("Recorder.Record", val) Return Default: Gosub, SendCC Return } Default: Gosub, SendCC Return } Return ; == HOTKEYS == ; ============= Volume_Mute:: b0M := Round(readParam("Bus[0].Mute")) ; Speakers b1M := Round(readParam("Bus[1].Mute")) ; Headphones b2M := Round(readParam("Bus[2].Mute")) ; Work Laptop Send b3M := Round(readParam("Bus[3].Mute")) ; Comms Send b4M := Round(readParam("Bus[4].Mute")) ; Recording cM := b0M + b1M + b2M + b3M + b4M if (cM = "5") { ; Unmute the ones that were unmuted before adjustToggle("Bus[0].Mute", b0Ms) ; Speakers adjustToggle("Bus[1].Mute", b1Ms) ; Headphones adjustToggle("Bus[2].Mute", b2Ms) ; Work Laptop Send adjustToggle("Bus[3].Mute", b3Ms) ; Comms Send adjustToggle("Bus[4].Mute", b4Ms) ; Recording Run %A_AhkPath% "flag.ahk" "off" } else { adjustToggle("Bus[0].Mute", True) ; Speakers adjustToggle("Bus[1].Mute", True) ; Headphones adjustToggle("Bus[2].Mute", True) ; Work Laptop Send adjustToggle("Bus[3].Mute", True) ; Comms Send adjustToggle("Bus[4].Mute", True) ; Recording b0Ms := b0M b1Ms := b1M b2Ms := b2M b3Ms := b3M b4Ms := b4M Run %A_AhkPath% "flag.ahk" "s" "red" } Return Volume_Up:: cM := Round(readParam("Strip[3].Mute")) if !(cM) { cLvl := readParam("Strip[3].Gain") if (cLvl != "") { cLvl += 1 adjustVolLvl("Strip[3].Gain", cLvl) } } Return Volume_Down:: cM := Round(readParam("Strip[3].Mute")) if !(cM) { cLvl := readParam("Strip[3].Gain") if (cLvl != "") { cLvl -= 1 adjustVolLvl("Strip[3].Gain", cLvl) } } 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") } adjustToggle(func, togg) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", func, "Float", togg, "Int") } adjustString(func, str) { DllCall(VMR_FUNCTIONS["SetParameterFloat"], "AStr", func, "AStr", str, "Str") } fader_to_fader(val) ; Translates MIDI fader values to the VoiceMeeter software faders { ; The upper limit for faders in VM is 12.0 ; The lower limit for the faders in VM that I would like is -40.0, how low it should go when the physical fader is all the way at the bottom nval := Round(((val / 127) * 72) - 60) Return nval } ; Formula: ((value / max value) * total range) - negative part of range dial_to_pan(val) { nval := Round((val / 127) - 0.5, 2) Return nval } 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 }