AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Vista Audio Control Functions v2.0
Goto page 1, 2, 3, 4  Next
 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Tue Oct 02, 2007 1:47 pm    Post subject: Vista Audio Control Functions v2.0 Reply with quote

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.
Code:
COM_Init()


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
View user's profile Send private message
Seabiscuit



Joined: 07 Jan 2007
Posts: 109
Location: In fund pe scaun, la o bere prin Romania :D

PostPosted: Wed Oct 03, 2007 1:20 am    Post subject: Reply with quote

I wanted this! I had problems with some of the OSC sound controls scripts working in Vista. Mes omages! Very Happy
_________________
Backgammon addicted!
GamesGrid was one of the first online web sites to bring Backgammon to the Internet
Back to top
View user's profile Send private message Yahoo Messenger
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Wed Oct 03, 2007 1:40 am    Post subject: Reply with quote

I'm glad it was of use to someone. Smile I wrote it so that I could mute/unmute line in. Laughing
Back to top
View user's profile Send private message
tuna



Joined: 03 Oct 2007
Posts: 40
Location: Bristol, England

PostPosted: Fri Oct 05, 2007 8:44 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sat Oct 06, 2007 3:03 am    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sat Oct 06, 2007 3:52 am    Post subject: Reply with quote

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
View user's profile Send private message
tuna



Joined: 03 Oct 2007
Posts: 40
Location: Bristol, England

PostPosted: Sat Oct 06, 2007 8:41 am    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sat Oct 06, 2007 10:16 am    Post subject: Reply with quote

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
View user's profile Send private message
tuna



Joined: 03 Oct 2007
Posts: 40
Location: Bristol, England

PostPosted: Sat Oct 06, 2007 1:06 pm    Post subject: Reply with quote

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
View user's profile Send private message
tuna



Joined: 03 Oct 2007
Posts: 40
Location: Bristol, England

PostPosted: Sat Oct 06, 2007 1:14 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sat Oct 06, 2007 1:48 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Sun Nov 11, 2007 6:09 am    Post subject: Reply with quote

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
View user's profile Send private message
bmcclure



Joined: 24 Nov 2007
Posts: 446

PostPosted: Mon Dec 10, 2007 6:29 pm    Post subject: Reply with quote

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
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2558
Location: Australia, Qld

PostPosted: Tue Dec 11, 2007 1:07 am    Post subject: Reply with quote

bmcclure wrote:
To clarify, this will only work in Vista, right?
Unfortunately, that is correct.
Back to top
View user's profile Send private message
bmcclure



Joined: 24 Nov 2007
Posts: 446

PostPosted: Tue Dec 11, 2007 1:32 am    Post subject: Reply with quote

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
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Goto page 1, 2, 3, 4  Next
Page 1 of 4

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group