I'm trying to create a script that will trigger a sound effect when a hotkey is pressed, this might not seem too difficult, but I also want to be able to output it to a specific audio device, a virtual audio cable in my case.
I've looked around on the internet a bit and I saw many possible solutions, but none have worked out for me (yet). It appears that the normal SoundPlay function in AHK can not output to a specific audio device, but I'm also looking into other options, like playing the sound via a batch script with wmplayer or other media players, but I can't find a solutions that will let me output the sound to a specific audio devices...
So my question is, what's the best way to play sound to a specific non-default audio device (a virtual audio cable) which can be done inside a command prompt or inside autohotkey?
So I managed to do what I was trying to accomplish. I'll tell you how I did it:
After some searching around on the internet I came across a C# library called IrrKlang. Using IrrKlang I made a little console app program which I can call by playsound soundfile.mp3 0, playsound is the the name of the .exe, the first parameter is the path to the soundfile from the location of the playsound.exe, and the last parameter is a number which is used to choose the audio device, which number this should be is still guess work but after some trial and error you can find the number of your virtual audio cable or other audio device.
For the people who come here in the future I've put my code up on github.
Everything you need is here:
Lexikos's Vista Audio Control Functions
Note: SoundSet and SoundGet on AutoHotkey v1.1.10 and later support Vista and later natively. You don't need VA.ahk unless you want to use advanced functions not supported by SoundSet/SoundGet.
https://autohotkey.com/board/topic/21984-vista-audio-control-functions/
To change the default output device, you can script the Sound properties in cpanel:
Run, mmsys.cpl
WinWait, Sound
ControlSend, SysListView321,{Down num} ' num matches device position
Sleep, 100
ControlClick, &Set Default
Sleep, 100
ControlClick, OK
WinWaitClose, Sound
Hth,
I modified one code i found, and add volume up and down.
You have to chance device1 and device2 names
Alt + Wheel Up = Volume Up
Alt + Wheel Down = Volume Down
Ctrl + F12 = Chance Device
glhf!
device1:="Speakers / Headphones"
device2:="Communications Headphones"
; http://www.daveamenta.com/2011-05/programmatically-or-command-line-change-the-default-sound-playback-device-in-windows-7/
Devices := {}
IMMDeviceEnumerator := ComObjCreate("{BCDE0395-E52F-467C-8E3D-C4579291692E}", "{A95664D2-9614-4F35-A746-DE8DB63617E6}")
; IMMDeviceEnumerator::EnumAudioEndpoints
; eRender = 0, eCapture, eAll
; 0x1 = DEVICE_STATE_ACTIVE
DllCall(NumGet(NumGet(IMMDeviceEnumerator+0)+3*A_PtrSize), "UPtr", IMMDeviceEnumerator, "UInt", 0, "UInt", 0x1, "UPtrP", IMMDeviceCollection, "UInt")
ObjRelease(IMMDeviceEnumerator)
; IMMDeviceCollection::GetCount
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+3*A_PtrSize), "UPtr", IMMDeviceCollection, "UIntP", Count, "UInt")
Loop % (Count)
{
; IMMDeviceCollection::Item
DllCall(NumGet(NumGet(IMMDeviceCollection+0)+4*A_PtrSize), "UPtr", IMMDeviceCollection, "UInt", A_Index-1, "UPtrP", IMMDevice, "UInt")
; IMMDevice::GetId
DllCall(NumGet(NumGet(IMMDevice+0)+5*A_PtrSize), "UPtr", IMMDevice, "UPtrP", pBuffer, "UInt")
DeviceID := StrGet(pBuffer, "UTF-16"), DllCall("Ole32.dll\CoTaskMemFree", "UPtr", pBuffer)
; IMMDevice::OpenPropertyStore
; 0x0 = STGM_READ
DllCall(NumGet(NumGet(IMMDevice+0)+4*A_PtrSize), "UPtr", IMMDevice, "UInt", 0x0, "UPtrP", IPropertyStore, "UInt")
ObjRelease(IMMDevice)
; IPropertyStore::GetValue
VarSetCapacity(PROPVARIANT, A_PtrSize == 4 ? 16 : 24)
VarSetCapacity(PROPERTYKEY, 20)
DllCall("Ole32.dll\CLSIDFromString", "Str", "{A45C254E-DF1C-4EFD-8020-67D146A850E0}", "UPtr", &PROPERTYKEY)
NumPut(14, &PROPERTYKEY + 16, "UInt")
DllCall(NumGet(NumGet(IPropertyStore+0)+5*A_PtrSize), "UPtr", IPropertyStore, "UPtr", &PROPERTYKEY, "UPtr", &PROPVARIANT, "UInt")
DeviceName := StrGet(NumGet(&PROPVARIANT + 8), "UTF-16") ; LPWSTR PROPVARIANT.pwszVal
DllCall("Ole32.dll\CoTaskMemFree", "UPtr", NumGet(&PROPVARIANT + 8)) ; LPWSTR PROPVARIANT.pwszVal
ObjRelease(IPropertyStore)
ObjRawSet(Devices, DeviceName, DeviceID)
}
ObjRelease(IMMDeviceCollection)
Return
$!WheelUp::Send {Volume_Up 5}
$!WheelDown::Send {Volume_Down 5}
currentDevice:=false
^F12::
currentDevice:=!currentDevice
if currentDevice
SetDefaultEndpoint( GetDeviceID(Devices, device1) )
else
SetDefaultEndpoint( GetDeviceID(Devices, device2) )
return
SetDefaultEndpoint(DeviceID)
{
IPolicyConfig := ComObjCreate("{870af99c-171d-4f9e-af0d-e63df40c2bc9}", "{F8679F50-850A-41CF-9C72-430F290290C8}")
DllCall(NumGet(NumGet(IPolicyConfig+0)+13*A_PtrSize), "UPtr", IPolicyConfig, "UPtr", &DeviceID, "UInt", 0, "UInt")
ObjRelease(IPolicyConfig)
}
GetDeviceID(Devices, Name)
{
For DeviceName, DeviceID in Devices
If (InStr(DeviceName, Name))
Return DeviceID
}
Related
I am writing an AHK script that uses audio devices. I'm using the Vista Audio library, and I have added some more functions to get additional data about the devices beyond their name. These functions are to get the device description and device icon:
GetDeviceDesc(device)
{
static PKEY_Device_DeviceDesc
if !VarSetCapacity(PKEY_Device_DeviceDesc)
VarSetCapacity(PKEY_Device_DeviceDesc, 20)
,VA_GUID(PKEY_Device_DeviceDesc :="{A45C254E-DF1C-4EFD-8020-67D146A850E0}")
,NumPut(2, PKEY_Device_DeviceDesc, 16)
VarSetCapacity(prop, 16)
VA_IMMDevice_OpenPropertyStore(device, 0, store)
; store->GetValue(.., [out] prop)
DllCall(NumGet(NumGet(store+0)+5*A_PtrSize), "ptr", store, "ptr", &PKEY_Device_DeviceDesc, "ptr", &prop)
ObjRelease(store)
VA_WStrOut(deviceDesc := NumGet(prop,8))
return deviceDesc
}
GetDeviceIcon(device)
{
static PKEY_DrvPkg_Icon
if !VarSetCapacity(PKEY_DrvPkg_Icon)
VarSetCapacity(PKEY_DrvPkg_Icon, 20)
,VA_GUID(PKEY_DrvPkg_Icon :="{CF73BB51-3ABF-44A2-85E0-9A3DC7A12132}")
,NumPut(4, PKEY_DrvPkg_Icon, 16)
VarSetCapacity(prop, 16)
VA_IMMDevice_OpenPropertyStore(device, 0, store)
; store->GetValue(.., [out] prop)
DllCall(NumGet(NumGet(store+0)+5*A_PtrSize), "ptr", store, "ptr", &PKEY_DrvPkg_Icon, "ptr", &prop)
ObjRelease(store)
VA_WStrOut(deviceIcon := NumGet(prop,8))
return deviceIcon
}
Both functions are slightly modified versions of the VA_GetDeviceName function from VA.ahk:
VA_GetDeviceName(device)
{
static PKEY_Device_FriendlyName
if !VarSetCapacity(PKEY_Device_FriendlyName)
VarSetCapacity(PKEY_Device_FriendlyName, 20)
,VA_GUID(PKEY_Device_FriendlyName :="{A45C254E-DF1C-4EFD-8020-67D146A850E0}")
,NumPut(14, PKEY_Device_FriendlyName, 16)
VarSetCapacity(prop, 16)
VA_IMMDevice_OpenPropertyStore(device, 0, store)
; store->GetValue(.., [out] prop)
DllCall(NumGet(NumGet(store+0)+5*A_PtrSize), "ptr", store, "ptr", &PKEY_Device_FriendlyName, "ptr", &prop)
ObjRelease(store)
VA_WStrOut(deviceName := NumGet(prop,8))
return deviceName
}
The first of my functions, to get the device description works, but the other to get the icon path returns nothing. In addition to trying to get the DrvPkg_Icon, I have also attempted to get the DeviceClass_Icon and DeviceClass_IconPath with no success.
I have the PKEY reference here and the IProperty method number reference here.
I can't figure out what I'm doing wrong. Is it possible that the code is working correctly and that these values are empty (according to the WinAPI documentation, a device is only guaranteed to have a friendly name, a description, and an interface friendly description)? In which case is there another way for me to get the device icons?
I just don't get why it's not working. Thank you
Consider the following snippet:
FormatTime, time,
Gui, Add, Text, vcTime,
GuiControl, , cTime, % time
Gui, Show, NoActivate Center AutoSize
AutoSize is based on the initial value from Gui, Add, Text, vcTime and not on the new value set by GuiControl, , cTime, % time. Depending on months etc., %time% length can vary. How can I automatically resize the window to adapt to updated values of %time%?
`
AutoSize is actually based on the current control sizes when Gui Show is called. The issue is that the "blank" subcommand to GuiControl for text controls does not automatically resize the control; it just changes the text and you still have to call GuiControl Move with a new size yourself. So, in your example, if you replace AutoSize with w200 the text will still be cut off at the same point.
As far as I know, there's not really a "built-in" automatic way to resize the text control based on the new text. The closest way is to use AHK's initial size calculation when creating a text control: create a new text control with the desired text, use GuiControlGet to get the size of the new control, and finally set the size of the original control to that size using GuiControl Move. Here's an example function which does this, adapted from here:
SetTextAndResize(controlHwnd, newText, fontOptions := "", fontName := "") {
Gui 9:Font, %fontOptions%, %fontName%
Gui 9:Add, Text, R1, %newText%
GuiControlGet T, 9:Pos, Static1
Gui 9:Destroy
GuiControl,, %controlHwnd%, %newText%
GuiControl Move, %controlHwnd%, % "h" TH " w" TW
}
Which would fit into your example like so:
FormatTime, time,
Gui, Add, Text, HwndTimeHwnd vcTime,
SetTextAndResize(TimeHwnd, time)
Gui, Show, NoActivate Center AutoSize
Now whenever you use SetTextAndResize instead of just setting the text you can use Gui Show, AutoSize to automatically resize the window correctly. Note that if you changed the font with Gui Font before adding the text control you would have to pass those same options to SetTextAndResize.
Alternatively, I looked at how AHK itself calculates the initial size of a text control for Gui Add, Text when none is provided and found it uses the Windows API function DrawText with DT_CALCRECT. Here's another implementation of SetTextAndResize I wrote using this directly:
SetTextAndResize(controlHwnd, newText) {
dc := DllCall("GetDC", "Ptr", controlHwnd)
; 0x31 = WM_GETFONT
SendMessage 0x31,,,, ahk_id %controlHwnd%
hFont := ErrorLevel
oldFont := 0
if (hFont != "FAIL")
oldFont := DllCall("SelectObject", "Ptr", dc, "Ptr", hFont)
VarSetCapacity(rect, 16, 0)
; 0x440 = DT_CALCRECT | DT_EXPANDTABS
h := DllCall("DrawText", "Ptr", dc, "Ptr", &newText, "Int", -1, "Ptr", &rect, "UInt", 0x440)
; width = rect.right - rect.left
w := NumGet(rect, 8, "Int") - NumGet(rect, 0, "Int")
if oldFont
DllCall("SelectObject", "Ptr", dc, "Ptr", oldFont)
DllCall("ReleaseDC", "Ptr", controlHwnd, "Ptr", dc)
GuiControl,, %controlHwnd%, %newText%
GuiControl Move, %controlHwnd%, % "h" h " w" w
}
I'm not sure how it compares to the first method in terms of performance, but one advantage is that it gets the font based on the control itself rather than having to provide it to the function yourself.
In Media Player Classic I found a way to jump to a point in a video/audio programmatically, avoiding the Go To... box.
The jump distances are available at Options → Tweaks,
and HKEY_CURRENT_USER\Software\MPC-HC\MPC-HC\Settings
(JumpDistL/JumpDistM/JumpDistS).
What I do is find the jump distances in the address space of Media Player Classic, and set the value of the large jump distance such
that if you applied it to the elapsed time you would get the desired time.
I then send a WM_COMMAND message with parameter 903/904 (all via AutoHotkey. I get the elapsed time by retrieving/parsing the contents of the Edit control.)
Because the jump is relative to the current point, it is imprecise,
and arrives within a second of the right time, but doesn't arrive
at exactly the same point each time.
Is there a more direct way of accomplishing this and if not,
would any Media Player Classic users/programmers
consider discussing on the forum, introducing new WM_COMMAND messages
that allow jump to point (in milliseconds),
or that retrieve the numerical values listed here
(state, position, duration, volumelevel, muted, playbackrate, reloadtime).
(The method found here is too slow to get the time accurately, and requires special options be set).
Thanks to the message from wOxxOm, below the question,
I have been able to create this AutoHotkey script,
which solves my original problem:
to set the elapsed time in Media Player Classic programmatically,
directly, without using the Go To... box.
It also solves the problem of retrieving
information about the video.
The hotkeys are:
- Ctrl+Q to start the MPC API,
- Ctrl+W to retrieve information,
- the number keys to jump partway through the video.
;==================================================
^q:: ;start MPC API
hWnd := A_ScriptHwnd+0
OnMessage(WM_COPYDATA:=74, "On_WM_COPYDATA")
;64-bit
Run, "C:\Program Files (x86)\K-Lite Codec Pack\MPC-HC64\mpc-hc64.exe" /slave %hWnd%
;32-bit
;Run, "C:\Program Files (x86)\K-Lite Codec Pack\MPC-HC\mpc-hc.exe" /slave %hWnd%
Return
;==================================================
^w:: ;display information
Send(vMPCApiHWnd, 0xA0003004, "") ;CMD_GETCURRENTPOSITION := 0xA0003004
vElapsed := 19990101
vDuration := 19990101
vElapsed += vMPCApiCurrent, S
vDuration += vMPCApiDuration, S
if (vMPCApiCurrent >= 3600) OR (vMPCApiDuration >= 3600)
vFormat := "HH:mm:ss"
else
vFormat := "mm:ss"
FormatTime, vElapsed, %vElapsed%, %vFormat%
FormatTime, vDuration, %vDuration%, %vFormat%
SplitPath, vMPCApiPath, vName, vDir, vExt, vNameNoExt, vDrive
vText = ;continuation section
(
title: %vMPCApiTitle%
author: %vMPCApiAuthor%
description: %vMPCApiDesc%
name: %vName%
path: %vMPCApiPath%
elapsed: %vElapsed% (%vMPCApiCurrent%)
duration: %vDuration% (%vMPCApiDuration%)
)
MsgBox %vText%
Return
;==================================================
#IfWinActive, ahk_class MediaPlayerClassicW
0:: ;skip to point
1::
2::
3::
4::
5::
6::
7::
8::
9::
vNum := SubStr(A_ThisHotkey, 1-1)
vElapsed2 := Round(vMPCApiDuration*(vNum/10))
Send(vMPCApiHWnd, 0xA0002000, "" vElapsed2) ;CMD_SETPOSITION := 0xA0002000
Return
#IfWinActive
;==================================================
On_WM_COPYDATA(wParam, lParam, msg, hwnd)
{
global vMPCApiHWnd
global vMPCApiTitle
global vMPCApiAuthor
global vMPCApiDesc
global vMPCApiPath
global vMPCApiDuration
global vMPCApiCurrent
dwData := NumGet(lParam+0, 0)
cbData := NumGet(lParam+A_PtrSize)
lpData := NumGet(lParam + 2*A_PtrSize)
lpData := StrGet(lpData)
if (dwData = 0x50000000) ;CMD_CONNECT := 0x50000000
{
vMPCApiHWnd := lpData
WinGetClass, vWinClass, ahk_id %vMPCApiHWnd%
if (vWinClass = "MediaPlayerClassicW")
MsgBox, , , MPC API on, 3
}
if (dwData = 0x50000003) ;CMD_NOWPLAYING := 0x50000003
{
StringSplit, lpData, lpData, |
vMPCApiTitle := lpData1
vMPCApiAuthor := lpData2
vMPCApiDesc := lpData3
vMPCApiPath := lpData4
vMPCApiDuration := lpData5
}
if (dwData = 0x50000007) ;CMD_CURRENTPOSITION := 0x50000007
vMPCApiCurrent := lpData
Return true
}
;==================================================
Send(Hwnd, dwData, lpData)
{
static WM_COPYDATA := 0x4a
VarSetCapacity(COPYDATASTRUCT, 3*A_PtrSize, 0)
cbData := (StrLen(lpData) + 1) * (A_IsUnicode ? 2 : 1)
NumPut(dwData, COPYDATASTRUCT, 0)
NumPut(cbData, COPYDATASTRUCT, A_PtrSize)
NumPut(&lpData, COPYDATASTRUCT, 2*A_PtrSize)
SendMessage, % WM_COPYDATA, % A_ScriptHwnd , ©DATASTRUCT,, % "ahk_id " Hwnd
return ErrorLevel == "FAIL" ? false : true
}
;==================================================
;USEFUL LINKS
;Sending Strings Via SendMessage - Ask for Help - AutoHotkey Community
;https://autohotkey.com/board/topic/98334-sending-strings-via-sendmessage/
;Media Player Classic - Homecinema MPC remote API (via WM_COPYDATA) - AutoIt Example Scripts - AutoIt Forums
;https://www.autoitscript.com/forum/topic/85354-media-player-classic-homecinema-mpc-remote-api-via-wm_copydata/
;mpcapi.h
;https://raw.githubusercontent.com/jeeb/mpc-be/master/src/apps/mplayerc/mpcapi.h
;winapi - media player classic - jump to point in video/audio programmatically - Stack Overflow
;http://stackoverflow.com/questions/41310778/media-player-classic-jump-to-point-in-video-audio-programmatically
;==================================================
USEFUL LINKS:
Sending Strings Via SendMessage - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/98334-sending-strings-via-sendmessage/
Media Player Classic - Homecinema MPC remote API (via WM_COPYDATA) - AutoIt Example Scripts - AutoIt Forums
https://www.autoitscript.com/forum/topic/85354-media-player-classic-homecinema-mpc-remote-api-via-wm_copydata/
mpcapi.h
https://raw.githubusercontent.com/jeeb/mpc-be/master/src/apps/mplayerc/mpcapi.h
So I use a clock replacement program. The problem is it also hijack clicks on the clock. So whenever I click on the clock in the notification area, program's popup menu launch rather than the default windows clock widget.
I also tried AHK ControlClick on TrayClockWClass. I still didn't get original widget. Is there any way to launch the original widget programmatically? I use Windows 10 1607.
I am fine with RunDll, API, SendMessage or any other way whatsoever.
Somebody upvoted my question today. So maybe they are looking for the answer. I posted it on autohotkey.com forums back then, and I get the answer there.
https://autohotkey.com/boards/viewtopic.php?t=21274
ControlGet, hClock, Hwnd,, TrayClockWClass1, ahk_class Shell_TrayWnd ; https://autohotkey.com/board/topic/70770-win7-taskbar-clock-toggle/
if (hClock) {
VarSetCapacity(IID_IAccessible, 16), DllCall("ole32\CLSIDFromString", "WStr", "{618736e0-3c3d-11cf-810c-00aa00389b71}", "Ptr", &IID_IAccessible)
if (DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hClock, "UInt", OBJID_CLIENT := 0xFFFFFFFC, "Ptr", &IID_IAccessible, "Ptr*", accTrayClock))
return
VarSetCapacity(variant, A_PtrSize == 8 ? 24 : 16, 0), NumPut(VT_I4 := 3, variant,, "UShort")
if (A_PtrSize == 4) ; https://autohotkey.com/boards/viewtopic.php?p=111355#p111355
DllCall(NumGet(NumGet(accTrayClock+0)+25*A_PtrSize), "Ptr", accTrayClock, "Int64", NumGet(variant, 0, "Int64"), Int64, NumGet(variant, 8, "Int64")) ; IAccessible::DoDefaultAction
else
DllCall(NumGet(NumGet(accTrayClock+0)+25*A_PtrSize), "Ptr", accTrayClock, "Ptr", &variant) ; IAccessible::DoDefaultAction
ObjRelease(accTrayClock)
}
When debugging scripts, you often need to know the value of your variables to know exactly what is going on. Outputting variables with MsgBox to tackle this problem is annoying and inefficient.
Is there a function that can help me with debugging variables?
I want it to write down all my local variables, their names and corresponding values as well as the name of the current procedure being executed to an ini file. Are there debugging tools that can provide this functionality, possibly in real time?
The name of the procedure may be challenging to do, but is there a way that at least all the local variables can get enumerated and written away?
Take a look at the ListVars command. It lists all of the variables.
You can also get more extensive information using a method like this:
MsgBox, % GetAhkStats("lines")
MsgBox, % GetAhkStats("variables")
MsgBox, % GetAhkStats("hotkeys")
Stat1 := GetAhkStats("history")
MsgBox, %Stat1%
Return
a::a
b::b
c::c
d::d
GetAhkStats(xxSection="", xxUseWindow=99, xxDestroyAfter=1)
{
xxSectionN = Lines|Variables|Hotkeys|History
If xxSection=
xxSection = History
Loop, Parse, xxSectionN, |
IfInString, A_LoopField, %xxSection%
xxSection = %A_Index%
DetectHiddenWindows, On
SetTitleMatchMode, 2
Gui, %xxUseWindow%:Show, Hide
xxHidWin := WinExist(A_ScriptFullPath " - AutoHotkey v")
xxOldpar := DllCall("GetParent", "UInt", xxHidWin)
DllCall("SetParent", "UInt", xxHidWin, "UInt", (GuiGetHWND("", xxUseWindow)))
WinMenuSelectItem, ahk_id %xxHidWin%,, View, %xxSection%&
Loop {
Sleep, 50
ControlGetText, xxOut1, Edit1, ahk_id %xxHidWin%
If xxOut1<>
break
}
WinHide, ahk_id %xxHidWin%
DllCall("SetParent", "UInt", xxHidWin, "UInt", xxOldpar)
If (xxDestroyAfter)
Gui, %xxUseWindow%:Destroy
Return, xxOut1
}
GuiGetHWND(xxClassNN="", xxGUI=1)
{
If (xxGUI)
Gui, %xxGUI%:+LastFound
xxGui_hwnd := WinExist()
If xxClassNN=
Return, xxGui_hwnd
ControlGet, xxOutputVar, Hwnd,, %xxClassNN%, ahk_id %xxGui_hwnd%
Return, xxOutputVar
}
Source
listvars does the trick for me.