 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Tue Oct 02, 2007 1:47 pm Post subject: Vista Audio Control Functions v2.0 |
|
|
Vista Audio Control Functions v2.0
VA provides Windows Vista-compatible alternatives to some SoundSet/SoundGet subcommands, as well as some additional features that SoundSet/SoundGet do not support. See the online documentation for a list of functions.
New in v2: Support for multiple devices.
Script Requirements:
Caution:
COM must be initialized prior to calling any VA functions.
Download with documentation
Online Documentation
GUI: Device Topology / Subunits
(I've made only the minimum changes for this to work with v2; I plan to eventually rewrite this script to take advantage of v2 features.)
Subunit/component names are defined by the audio drivers, so will vary from PC to PC. Additionally, volume and mute subunits may have inconsistent names. The following script can be used to determine which volume and mute subunits are available:
| Code: | #NoEnv
COM_CoInitialize()
pIPart := VA_GetSpeakersIPart()
text1 := VA_EnumerateSubParts_List(pIPart)
text2 := VA_EnumerateSubParts_Tree(pIPart)
COM_Release(pIPart)
Gui, Font,, Lucida Console
Gui, Add, Edit, H550 W140, %text1%
Gui, Font, s10
Gui, Add, Edit, H550 W620 YM -Wrap +0x100000, %text2%
Gui, Show,, Vista Audio Device Topology
Gui, +LastFound
WinWaitActive
Send ^{Home}
return
GuiClose:
ExitApp
; Shows a list of mute/volume components.
VA_EnumerateSubParts_List(part)
{
iid_vol := "{7FB7B48F-531D-44A2-BCB3-5AD5A134B3DC}", COM_GUID4String(iid_vol,iid_vol)
iid_mute := "{DF45AEEA-B74A-4B6B-AFAD-2366B6AA012E}", COM_GUID4String(iid_mute,iid_mute)
VA_EnumerateSubParts_List_(part, list_vol, list_mute, &iid_vol, &iid_mute)
return "VOLUME SUBUNITS`n`n" list_vol "`n`nMUTE SUBUNITS`n`n" list_mute
}
VA_EnumerateSubParts_List_(part, ByRef list_vol, ByRef list_mute, piid_vol, piid_mute)
{
static S_OK=0
; part->GetPartType()
DllCall(NumGet(NumGet(part+0)+24), "uint",part, "uint*",type)
if (type = 1) ; Subunit
{
; part->GetName(...)
; [out] LPWSTR* pwname -- (pointer to Unicode string)
DllCall(NumGet(NumGet(part+0)+12), "uint",part, "uint*",pwname)
name := COM_Ansi4Unicode(pwname), COM_CoTaskMemFree(pwname)
; part->Activate(...)
; [out] IAudioVolumeLevel iface
if (S_OK = DllCall(NumGet(NumGet(part+0)+52), "uint",part, "uint",1, "uint",piid_vol, "uint*",iface))
COM_Release(iface), list_vol .= name "`n"
; [out] IAudioMute iface
if (S_OK = DllCall(NumGet(NumGet(part+0)+52), "uint",part, "uint",1, "uint",piid_mute, "uint*",iface))
COM_Release(iface), list_mute .= name "`n"
}
; part->EnumPartsIncoming(...)
; [out] IPartsList parts
DllCall(NumGet(NumGet(part+0)+40), "uint",part, "uint*",parts)
; parts->GetCount()
DllCall(NumGet(NumGet(parts+0)+12), "uint",parts, "uint*",count)
Loop, %count%
{
; parts->GetPart(A_Index-1, [out] subpart)
DllCall(NumGet(NumGet(parts+0)+16), "uint",parts, "uint",A_Index-1, "uint*",subpart)
; RECURSE
VA_EnumerateSubParts_List_(subpart, list_vol, list_mute, piid_vol, piid_mute)
COM_Release(subpart)
}
}
; Shows the device topology in tree form.
VA_EnumerateSubParts_Tree(part)
{
static indent, indent_size=3
; Friendly names for common interfaces.
static iid_DF45AEEA_B74A_4B6B_AFAD_2366B6AA012E="IAudioMute"
, iid_7FB7B48F_531D_44A2_BCB3_5AD5A134B3DC="IAudioVolumeLevel"
, iid_85401FD4_6DE4_4B9D_9869_2D6753A82F3C="IAudioAutoGainControl"
; part->GetName(...)
; [out] LPWSTR* pwname -- (pointer to Unicode string)
DllCall(NumGet(NumGet(part+0)+12), "uint",part, "uint*",pwname)
; part->GetPartType()
DllCall(NumGet(NumGet(part+0)+24), "uint",part, "uint*",type)
name := COM_Ansi4Unicode(pwname), COM_CoTaskMemFree(pwname)
text .= indent "+ "
if (type != 1) ; not a subunit
text .= type=0 ? "(CONNECTOR) " : "(UNKNOWN) "
text .= name
; connPart->GetControlInterfaceCount(count)
DllCall(NumGet(NumGet(part+0)+32), "uint",part, "uint*",count)
Loop, %count%
{
; connPart->GetControlInterface(...)
; [out] IControlInterface idesc
DllCall(NumGet(NumGet(part+0)+36), "uint",part, "uint",A_Index-1, "uint*",idesc)
; idesc->GetIID(...)
; [out] GUID iid
VarSetCapacity(iid, 16)
DllCall(NumGet(NumGet(idesc+0)+16), "uint",idesc, "uint",&iid)
iid := COM_String4GUID(&iid)
StringReplace, iids, iid, -, _, All
StringTrimLeft, iids, iids, 1
StringTrimRight, iids, iids, 1
if iid_%iids%
iids := iid_%iids%
else
iids := iid
text .= A_Index>1 ? ", " iids : " : " iids
COM_Release(idesc), idesc=0
}
text .= "`n"
Loop, %indent_size%
indent .= A_Space
; part->EnumPartsIncoming(...)
; [out] IPartsList parts
DllCall(NumGet(NumGet(part+0)+40), "uint",part, "uint*",parts)
; parts->GetCount()
DllCall(NumGet(NumGet(parts+0)+12), "uint",parts, "uint*",count)
Loop, %count%
{
; parts->GetPart(A_Index-1)
DllCall(NumGet(NumGet(parts+0)+16), "uint",parts, "uint",A_Index-1, "uint*",subpart)
text .= VA_EnumerateSubParts_Tree(subpart)
COM_Release(subpart)
}
indent := SubStr(indent,1,-indent_size)
return text
}
; Gets a pointer to an IPart interface that represents the Speakers.
VA_GetSpeakersIPart()
{
defaultDevice := VA_GetDevice()
; defaultDevice->Activate(...)
; [out] IDeviceTopology deviceTopology
iid := "{2A07407E-6497-4A18-9787-32F79BD0D98F}"
DllCall(NumGet(NumGet(defaultDevice+0)+12), "uint",defaultDevice, "uint",COM_GUID4String(iid,iid), "uint",1, "uint",0, "uint*",deviceTopology)
COM_Release(defaultDevice), defaultDevice=0
; deviceTopology->GetConnector(0,...)
; [out] IConnector endptConnector
DllCall(NumGet(NumGet(deviceTopology+0)+16), "uint",deviceTopology, "uint",0, "uint*",endptConnector)
COM_Release(deviceTopology), deviceTopology=0
; endptConnector->GetConnectedTo(...)
; [out] IConnector hwdevConnector
DllCall(NumGet(NumGet(endptConnector+0)+32), "uint",endptConnector, "uint*",hwdevConnector)
COM_Release(endptConnector), endptConnector=0
; hwdevConnector->QueryInterface(...)
; [out] IPart connPart
iid := "{AE2DE0E4-5BCA-4F2D-AA46-5D13F8FDB3A9}"
connPart := COM_QueryInterface(hwdevConnector, iid)
COM_Release(hwdevConnector), hwdevConnector=0
return connPart
}
|
On the left side is a list of component/subunit names for the volume and mute functions. Example list: | Quote: | VOLUME SUBUNITS
Speakers
Mic Volume
Line Volume
CD Volume
Video Volume
Aux Volume
SPDIF
MIDI Volume
MUTE SUBUNITS
Master Mute
Mic Mute
Line Mute
CD Mute
Video Mute
Aux Mute
SPDIF
MIDI Mute | On the right is a textual tree representation of "the functional hardware topology of an audio rendering device." Supported interfaces are listed after " : " to the right of each node. Currently only IAudioVolumeLevel and IAudioMute are useful. (IAudioAutoGainControl may also be used if you know what you're doing.)
Last edited by Lexikos on Mon Feb 11, 2008 10:47 am; edited 6 times in total |
|
| Back to top |
|
 |
Seabiscuit
Joined: 07 Jan 2007 Posts: 109 Location: In fund pe scaun, la o bere prin Romania :D
|
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Wed Oct 03, 2007 1:40 am Post subject: |
|
|
I'm glad it was of use to someone. I wrote it so that I could mute/unmute line in.  |
|
| Back to top |
|
 |
tuna
Joined: 03 Oct 2007 Posts: 40 Location: Bristol, England
|
Posted: Fri Oct 05, 2007 8:44 pm Post subject: |
|
|
Sounds like a great app but i still cant get it to work correctly. The thing is VA_GETVOLUME() seems to retrieve the wrong volumes when compared to what is detailed by windows mixer, so i did a test:
| Code: | Windows Mixer vs VA_GETVOLUME()
0 0.000000
1 0.184100
2 0.391507
3 0.623866
4 0.878952
5 1.151728
6 1.451509
7 1.773915
8 2.112748
9 2.470650
10 2.854000
11 3.246838
12 3.671082
13 4.110560
14 4.560251
15 5.034180
16 5.531064
17 6.049241
18 6.586668
19 7.113557
20 7.679757
21 8.256726
22 8.874515
23 9.500057
24 10.129228
25 10.757594
26 11.423194
27 12.128233
28 12.827102
29 13.514300
30 14.236999
31 14.997031
32 15.796326
33 16.575456
34 17.327725
35 18.180182
36 19.003250
37 19.789590
38 20.683663
39 21.537840
40 22.426500
41 23.351034
42 24.223868
43 25.128676
44 26.066630
45 27.038945
46 28.046877
47 28.985548
48 30.064791
49 31.069872
50 32.108030
51 33.059484
52 34.163121
53 35.303077
54 36.347829
55 37.423088
56 38.529746
57 39.668718
58 40.840949
59 41.894696
60 43.131927
61 44.244104
62 45.549938
63 46.723785
64 47.927570
65 49.162057
66 50.428028
67 51.726288
68 53.057658
69 54.225826
70 55.620946
71 56.845049
72 58.306970
73 59.589685
74 60.900392
75 62.239699
76 63.608233
77 65.006629
78 66.435541
79 67.895633
80 69.387587
81 70.655721
82 72.207904
83 73.793958
84 75.142075
85 76.514664
86 78.194697
87 79.622695
88 81.076615
89 82.856196
90 84.368809
91 85.908878
92 87.476902
93 89.073389
94 90.698855
95 92.353825
96 94.038836
97 95.408841
98 97.149306
99 98.921363
100 100.000000
| [Moderator's note - added code tags to shrink]
Which ends up looking like this:
I simply wrote the values derived from currentSound := VA_GetVolume() to a csv file as a test - what could have gone wrong? Any suggestions?
Many thanks. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Sat Oct 06, 2007 3:03 am Post subject: |
|
|
| tuna wrote: | | Sounds like a great app but i still cant get it to work correctly. The thing is VA_GETVOLUME() seems to retrieve the wrong volumes when compared to what is detailed by windows mixer, | The APIs I used aren't really intended for simple master volume control. They only allow volume control using decibels (which is non-linear), whereas a more appropriate interface (for master volume only) allows decibels or scalar values (between 0 and 1.)
To use scalar values, I had to find an algorithm to calculate scalar from decibels, and vice versa. Since I had tested it (on "Line Volume") and the results seemed accurate, your results surprise me.
Actually, testing it again, it does exactly what it is supposed to: VA_GetVolume("Speakers") matches up with the volume reported by Speakers Properties (Levels tab.) It is strange that Speakers in Speakers Properties and Speakers in the Volume Mixer differ like that. I guess it's because the Speakers Properties tab uses the same interface and algorithm as I do to convert decibels <-> scalar values, whereas the Volume Mixer does not.
To get to Speakers Properties, right-click the volume tray icon, click Playback Devices, and on the Playback tab, double-click Speakers.
| Larry Osterman wrote: | | In Vista RTM, the SetMasterVolumeLevelScalar API runs the input volume (from 0.0 to 1.0) through a volume curve to produce a more linear volume experience - it's intended for use in application volume control sliders. | IAudioEndpointVolume value and SndVol not in Sync
(He is referring to the IAudioEndpointVolume interface, which is for master volume control. I use IAudioVolumeLevel, which works for Line In, Microphone, etc.) |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Sat Oct 06, 2007 3:52 am Post subject: |
|
|
I've updated the script, adding the following functions: | Code: | VA_GetMasterVolume( [ channel ] )
VA_SetMasterVolume( vol [, channel ] )
VA_GetMasterChannelCount()
VA_GetMasterMute()
VA_SetMasterMute( mute ) |
These use IAudioEndpointVolume, so they accurately reflect the volume levels reported by Vista's volume mixer. |
|
| Back to top |
|
 |
tuna
Joined: 03 Oct 2007 Posts: 40 Location: Bristol, England
|
Posted: Sat Oct 06, 2007 8:41 am Post subject: |
|
|
Thanks. However oddly enough it still doesnt seem to work too well (though the mute function seems to wrok fine - its just the get and set ones that seem to be having problems). Since you've fixed your code, its probably now something in my code:
| Code: | COM_CoInitialize()
currentSound := VA_GetMasterVolume()
; msgbox %currentSound%`n%vol% ;just for testing
wingetactivetitle, programName
winget, progID, id, %programName%
progPID := errorlevel
loop, 25
emptyBar = %EmptyBar%%volBarEmpty%
exit
Display:
currentSound := VA_GetVolume("master", "")
tooltip, Volume (%currentSound%`%): %curr%
presstime = %a_mday%%a_hour%%a_min%%a_sec%
presstime += 2
settimer, splashOff, On
return
ctrl_bn_volume:
g_buttonDown(1, "")
if ctrl_bn_volume = 1
{
VA_SetMute(1)
currentSound := VA_GetMasterVolume()
toolTipmessage = Unmute volume (at %currentSound%`%)
g_tooltipControl("" , toolTipmessage)
}
else
{
VA_SetMute(0)
g_tooltipControl("", "Mute volume")
}
return
volumeUp:
currentSound := VA_GetMasterVolume()
currentSound += 1
VA_SetMasterVolume(currentSound) ; Send {Volume_Up 2}
mark = 0
curr = %emptyBar%
loop
{
mark += 1
if mark <= %currentSound%
stringreplace, curr, curr, %volBarEmpty%, %volBarFull%
else
break
}
gosub, display
return
volumeDn:
currentSound := VA_GetMasterVolume()
currentSound -= 1
VA_SetMasterVolume(currentSound)
mark = 0
curr = %emptyBar%
loop
{
mark += 1
if mark <= %currentSound%
stringreplace, curr, curr, %volBarEmpty%, %volBarFull%
else
break
}
gosub, display
return |
Its a bit messy becuase i cut in halfway through the script. Any suggestions?
Many thanks |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Sat Oct 06, 2007 10:16 am Post subject: |
|
|
Perhaps this line: | Code: | | currentSound := VA_GetVolume("master", "") | On my system, there aren't any volume interfaces with "master" in the name. The master volume interface is called "Speakers". You should probably be using VA_GetMasterVolume(), anyway.
It's probably unrelated, but I guess this: | Code: | winget, progID, id, %programName%
progPID := errorlevel | is meant to be: | Code: | winget, progID, id, %programName%
winget, progPID, PID, %programName% |
|
|
| Back to top |
|
 |
tuna
Joined: 03 Oct 2007 Posts: 40 Location: Bristol, England
|
Posted: Sat Oct 06, 2007 1:06 pm Post subject: |
|
|
Cheers. Yeah the PID thing was a bit left over from a deleted section. I did another test using the exact same test script with currentSound := VA_GetMasterVolume() instead of VA_GetVolume() and got a set of perfect results. I think you've fixed the problem - just probably something a bit dodgy with my script - ill check it out.
Many thanks.
Last edited by tuna on Sat Oct 06, 2007 1:17 pm; edited 1 time in total |
|
| Back to top |
|
 |
tuna
Joined: 03 Oct 2007 Posts: 40 Location: Bristol, England
|
Posted: Sat Oct 06, 2007 1:14 pm Post subject: |
|
|
After a fine comb i think the suspect is
| Code: | | setformat, float, 0.0 |
when i remove it from the autoexcecute section, i seem to get a correct value with VA_GETMASTERVOLUME() (though va_getvolume() still doesnt seem to work).
...just in case anyone else is having similar problems. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Sat Oct 06, 2007 1:48 pm Post subject: |
|
|
| tuna wrote: | After a fine comb i think the suspect is | Code: | | setformat, float, 0.0 |
| Yes, that would do it. VA_GetMasterVolume() retrieves a floating-point value between 0.0 and 1.0. With SetFormat,Float,0.0, this value would be set to either 0 or 1 when it is stored in a variable (since DecimalPlaces is 0.) The return value is volume*100, which would be 0 or 100 (but never something in between.)
| Quote: | | (though va_getvolume() still doesnt seem to work). | If it doesn't work at all: Specifying "master" as the component (as in your previous post) is not likely to work; "Speakers" is the name you need. (For mute it is "Master Mute", or a substring like "master".) I explained this in my original post and again in the comments of the script. If the device topology script shows "master" in the VOLUME SUBINITS list, it should work (and you should disregard what I just said.)
If the component name isn't the issue, please be more specific as to the expected and actual effects of VA_GetVolume() in your script.
If it is reporting values that don't match the volume mixer: it isn't supposed to, as previously explained. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Sun Nov 11, 2007 6:09 am Post subject: |
|
|
New feature:
Detect when sound is playing through the default output device.
Example script:
| Code: | COM_CoInitialize()
if peakMeter := VA_GetDefaultAudioMeter()
{
Loop {
if GetKeyState("End")
break
ToolTip % VA_GetPeakValue(peakMeter) ; display peak value since last call
Sleep, 100
}
COM_Release(peakMeter)
} else
MsgBox Failed to get peak meter.
COM_CoUninitialize() |
(Be sure to update to the latest version of VA.ahk first.) |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 446
|
Posted: Mon Dec 10, 2007 6:29 pm Post subject: |
|
|
Hey this works great without needing UAC access!
To clarify, this will only work in Vista, right?
I currently run this if A_OSVersion = WIN_VISTA and run SoundSet otherwise, but I just want to make sure that's necessary.
Thanks for the great functions!
Edit: Maybe I spoke too soon. I seem to be having trouble now with VA_GetMasterVolume returning a blank value. It worked at first, and oddly enough, just stopped working on subsequent script runs.
Edit 2: Now it works again. Hmm. Perhaps just some odd Vista behavior. Seems fine now though. _________________ -Ben
SteamLab
SteamLab Wiki
[Broken] - My industrial music [on GarageBand] |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 2558 Location: Australia, Qld
|
Posted: Tue Dec 11, 2007 1:07 am Post subject: |
|
|
| bmcclure wrote: | | To clarify, this will only work in Vista, right? | Unfortunately, that is correct. |
|
| Back to top |
|
 |
bmcclure
Joined: 24 Nov 2007 Posts: 446
|
Posted: Tue Dec 11, 2007 1:32 am Post subject: |
|
|
Here's an OS-independent function that lets you increment, set, or mute/unmute the master volume. It uses SoundGet/SoundSet or your functions, depending on OS type.
| Code: | SetVol(pSetVol="") {
If (pSetVol="") {
curMute := (A_OSVersion = "WIN_VISTA") ? VA_GetMasterMute() : SoundGet("","Mute")
If (curMute = "On" or curMute = "Off")
curMute := (curMute = "On") ? 1 : 0
pSetVol := curMute ? "u" : "m"
}
If (SubStr(pSetVol,1,1) = "m") {
If (A_OSVersion = "WIN_VISTA")
VA_SetMasterMute(1)
Else SoundSet,% 1,,Mute
return "Muted"
} Else If (SubStr(pSetVol,1,1) = "u") {
If (A_OSVersion = "WIN_VISTA")
VA_SetMasterMute(0)
Else SoundSet,% 0,,Mute
return "Unmuted"
} Else {
curVol := Transform("Round", (A_OSVersion = "WIN_VISTA") ? VA_GetMasterVolume() : SoundGet())
If (SubStr(pSetVol,1,1) = "+") {
newVol := ((curVol + SubStr(pSetVol,2)) > 100) ? 100 : curVol + SubStr(pSetVol,2)
} Else If (SubStr(pSetVol,1,1) = "-") {
newVol := ((curVol - SubStr(pSetVol,2)) < 0) ? 0 : curVol - SubStr(pSetVol,2)
} Else If (pSetVol <= 100 and pSetVol >= 0){
newVol := pSetVol
} Else Return "Error"
If (A_OSVersion = "WIN_VISTA")
VA_SetMasterVolume(newVol)
Else SoundSet %newVol%
Return newVol
}
} |
Note: requires Titan's AHK functions and SoundGet and Transform. Otherwise it can be easily modified to use AHK's base commands
Pass nothing to toggle mute on and off - SetVol()
Pass "u" or "m" or "unmute" or "mute" to unmute or mute - SetVol("m")
Pass a +num or -num to increment the volume - SetVol("-5")
Pass a number between 1 and 100 to set the volume - SetVol(75)
Function will return either the new volume, "Muted"/"Unmuted", or "Error" _________________ -Ben
SteamLab
SteamLab Wiki
[Broken] - My industrial music [on GarageBand] |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|