LB Booster
Programming >> BASIC code examples >> Get and set master sound level
http://lbb.conforums.com/index.cgi?board=code&action=display&num=1476369211

Get and set master sound level
Post by RobM on Oct 11th, 2016, 10:11pm

Does LBB have the ability to get and set the master sound level? Maybe through some BBC BASIC code?
Re: Get and set master sound level
Post by Richard Russell on Oct 11th, 2016, 10:52pm

on Oct 11th, 2016, 10:11pm, RobM wrote:
Does LBB have the ability to get and set the master sound level? Maybe through some BBC BASIC code?

On Windows Vista and later you can call ISimpleAudioVolume::SetMasterVolume. Although this is a call to a method in a COM object, so would once have been considered impossible in native LB code, I proved that not to be the case by creating my CallMethod function.

The difficulty is likely to be finding the IID of the ISimpleAudioVolume object and the layout of its vTable. It looks from MSDN as if they should be in the audioclient.h file in the Windows Platform SDK.

Richard.

Re: Get and set master sound level
Post by RobM on Oct 11th, 2016, 11:35pm

OK, thanks anyway. This project isn't important enough for me to learn COM object programming.
Re: Get and set master sound level
Post by Rod on Oct 12th, 2016, 06:55am

Why the master volume? The general idea now is that the user is in control of the master volume and that programs only get to alter their own volume level.

That allows co existence with other programs that may need to output sound.

If the user has sound off that may be for a good reason.

There are examples posted of setting the program volume, flashing a massage to confirm they can hear sound is possible, perhaps at the start of the program.
Re: Get and set master sound level
Post by RobM on Oct 12th, 2016, 2:55pm

In this case, I am the user.

I go on YouTube and often times need to turn up the volume to hear the video. Then I forget to turn it back down. A while later I'll get an email and my 'new mail' wav is too loud.

What I was going to do is have a program run in the background, checking the master sound level every 10 seconds. after it sees the level has gone up it will wait about 10 minutes and then turn it back down.

Like I said nothing real important smiley
Re: Get and set master sound level
Post by Richard Russell on Oct 12th, 2016, 4:19pm

on Oct 12th, 2016, 2:55pm, RobM wrote:
Like I said nothing real important smiley

Actually I think it's quite a neat idea - I might use it myself if you get it working. There's some complete C++ code here which doesn't look as if it ought to be too difficult to translate to Liberty BASIC using my CallMethod function.

If you don't want to do the translation I probably could quite quickly. If there were enough interest I could code it in a verbose step-by-step fashion to show how anything similar can be tackled.

Richard.

Re: Get and set master sound level
Post by RobM on Oct 12th, 2016, 4:40pm

If you would do it that would be great. I might be able to translate some of that c++ code but I don't really think I could figure out how to apply your CallMethod function where needed.

For the record, I was able to get my idea working along with an AutoHotKey program. It is really simple in that language, "Soundget, x" & "Soundset, x". But every time the program runs Windows changes the cursor to the app_starting (busy) cursor and is 100 times more annoying than having the volume set too high.
Re: Get and set master sound level
Post by Richard Russell on Oct 12th, 2016, 5:46pm

on Oct 12th, 2016, 4:19pm, Richard Russell wrote:
If there were enough interest I could code it in a verbose step-by-step fashion

What say the assembled members? Is it worth the extra effort of me doing that? Is anybody (except me) ever likely to try writing COM-object code in Liberty BASIC?

Richard.

Re: Get and set master sound level
Post by RobM on Oct 12th, 2016, 5:56pm

Speaking for myself I would have to say I don't know if I would use it for anything other than this current idea. Knowing in the back of my mind that COM objects weren't possible with LB I never looked into what was possible in using them.

Offhand, do you have a general list of things that could be accomplished.
Re: Get and set master sound level
Post by Richard Russell on Oct 13th, 2016, 7:29pm

on Oct 12th, 2016, 5:56pm, RobM wrote:
Speaking for myself I would have to say I don't know if I would use it for anything other than this current idea.

OK, fair enough. There's a huge range of COM-based Windows API functions - pretty much any API introduced in the last ten years or so is probably a COM interface - but if it's not something you've ever wanted up to now, you probably never will.

Here's a direct, but not heavily commented, translation of the C++ code; it works in both LB 4 and LBB. It should be compatible with Windows Vista onwards:

Code:
    ' Translated to Liberty BASIC by Richard Russell from the code here, 13-Oct-2016:
    ' http://www.codeproject.com/Tips/233484/Change-Master-Volume-in-Visual-Cplusplus

    result = ChangeVolume(0.5, 1)
    end
    
function ChangeVolume(nVolume, bScalar)
    open "ole32.dll" for dll as #ole32
    open "oleaut32.dll" for dll as #oleaut32
    calldll #ole32, "CoInitialize", 0 as long, r as void

    IID.MMDeviceEnumerator$ = UUID$("{A95664D2-9614-4F35-A746-DE8DB63617E6}")
    CLSID.MMDeviceEnumerator$ = UUID$("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
    IID.IAudioEndpointVolume$ = UUID$("{5CDF2C82-841E-4546-9722-0CF74078229A}")
    CLSCTX.INPROC.SERVER = 1
    eRender = 0
    eConsole = 0
    Release = 2
    Activate = 3
    GetDefaultAudioEndpoint = 4
    SetMasterVolumeLevel = 6
    SetMasterVolumeLevelScalar = 7
    GetMasterVolumeLevel = 8
    GetMasterVolumeLevelScalar = 9
        
    struct IMM, deviceEnumerator as ulong
    calldll #ole32, "CoCreateInstance", CLSID.MMDeviceEnumerator$ as ptr, _
        0 as long, CLSCTX.INPROC.SERVER as long, IID.MMDeviceEnumerator$ as ptr, _
        IMM as struct, hr as long

    ' hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);    
    struct default, Device as ulong
    struct parms, dataFlow as long, role as long, ppDevice as ptr 
    parms.dataFlow.struct = eRender
    parms.role.struct = eConsole
    parms.ppDevice.struct = default.struct
    hr = CallMethod(IMM.deviceEnumerator.struct, GetDefaultAudioEndpoint, parms.struct)
    default.struct = parms.ppDevice.struct
    
    ' hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), 
    '      CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
    struct Iaudio, endpointVolume as ulong
    struct parms, iid as ptr, dwClsCtx as long, _
        pActivationParams as ulong, ppInterface as ptr
    parms.iid.struct = IID.IAudioEndpointVolume$
    parms.dwClsCtx.struct = CLSCTX.INPROC.SERVER
    parms.ppInterface.struct = Iaudio.struct
    hr = CallMethod(default.Device.struct, Activate, parms.struct)
    Iaudio.struct = parms.ppInterface.struct
    
    ' defaultDevice->Release();
    hr = CallMethod(default.Device.struct, Release, "")
    default.Device.struct = 0

    ' endpointVolume->GetMasterVolumeLevel(ĪtVolume);
    struct Volume, float as long 
    struct parms, pfLevel as ptr
    parms.pfLevel.struct = Volume.struct
    hr = CallMethod(Iaudio.endpointVolume.struct, GetMasterVolumeLevel, parms.struct)
    Volume.struct = parms.pfLevel.struct
    
    ' Convert float to double:
    struct current, Volume as double
    float = Volume.float.struct
    calldll #oleaut32, "VarR8FromR4", float as long, current as struct, r as void
    print "Current volume (dB) = "; current.Volume.struct
    
    ' hr = endpointVolume->GetMasterVolumeLevelScalar(ĪtVolume);
    hr = CallMethod(Iaudio.endpointVolume.struct, GetMasterVolumeLevelScalar, parms.struct)
    Volume.struct = parms.pfLevel.struct
    
    ' Convert float to double:
    float = Volume.float.struct
    calldll #oleaut32, "VarR8FromR4", float as long, current as struct, r as void
    print "Current volume (scalar) = "; current.Volume.struct
    
    ' Set new volume:
    struct parms, fLevel as ptr, EventContext as long
    calldll #oleaut32, "VarR4FromR8", nVolume as double, parms as struct, r as void
    if bScalar = 0 then
        hr = CallMethod(Iaudio.endpointVolume.struct, SetMasterVolumeLevel, parms.struct)
    else
        hr = CallMethod(Iaudio.endpointVolume.struct, SetMasterVolumeLevelScalar, parms.struct)    
    end if

    ' endpointVolume->Release();
    hr = CallMethod(Iaudio.endpointVolume.struct, Release, "")
    Iaudio.endpointVolume.struct = 0

    calldll #ole32, "CoUninitialize", r as void
    close #oleaut32
    close #ole32
end function       
    
function UUID$(iid$)
    l = len(iid$) + 1
    wide$ = space$(2 * l) + chr$(0)
    calldll #kernel32, "MultiByteToWideChar", 0 as long, 0 as long, _
                 iid$ as ptr, -1 as long, wide$ as ptr, l as long, r as long
    UUID$ = space$(16) + chr$(0)
    calldll #ole32, "CLSIDFromString", wide$ as ptr, UUID$ as ptr, r as long
end function

function CallMethod(object, method, parm$)
    code$ = chr$(139)+"D$"+chr$(4)+chr$(139)+"T$"+chr$(8)+chr$(139)+"L$" _
    + chr$(16)+"VW"+chr$(139)+"t$"+chr$(20)+chr$(43)+chr$(225)+chr$(139) _
    + chr$(252)+chr$(243)+chr$(164)+chr$(80)+chr$(139)+chr$(0)+chr$(255) _
    + chr$(20)+chr$(144)+chr$(95)+chr$(94)+chr$(194)+chr$(16)+chr$(0)
    p$ = parm$
    n = len(p$)
    calldll #user32, "CallWindowProcA", code$ as ptr, object as long,_
        method as long, p$ as ptr, n as long, CallMethod as long
end function  

It's really annoying that structs are global in LB, since it means I can't make this a library function that can safely be called from any program.

Richard.

Re: Get and set master sound level
Post by RobM on Oct 13th, 2016, 10:59pm

Thanks Richard!

I think I will add one more parameter to the function to specify whether it should check the sound level or change it. Looks like that should be easy enough.
Re: Get and set master sound level
Post by RobM on Oct 14th, 2016, 03:15am

I ended up splitting it into a function & a sub and removed some (but not all) unneeded code. As is, this code will lower your volume 6 seconds after it sees it over 10.

My loops here are pretty ugly coding but this is just a quick demo :)

Thanks again Richard.

Code:
    DesiredVolume=10'volume as a percentage from 0 to 100, as Windows shows it in the master volume control
    ResetTime=.1'time in minutes to wait after volume has been increased
[loop1]
    scan
    calldll #kernel32, "Sleep", 10 as long, re as void
    CurrentVolume=GetVolume()
    if CurrentVolume > DesiredVolume then
      StartTime=GetTickCount()
      goto [loop2]
    end if
    goto [loop1]

[loop2]
    scan
    calldll #kernel32, "Sleep", 10 as long, re as void
    CurrentTime=GetTickCount()
    if CurrentTime-StartTime > ResetTime*60*1000 then
      call SetVolume DesiredVolume,1
      goto [loop1]
    end if
    goto [loop2]

function GetVolume()
    open "ole32.dll" for dll as #ole32
    open "oleaut32.dll" for dll as #oleaut32
    calldll #ole32, "CoInitialize", 0 as long, r as void

    IID.MMDeviceEnumerator$ = UUID$("{A95664D2-9614-4F35-A746-DE8DB63617E6}")
    CLSID.MMDeviceEnumerator$ = UUID$("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
    IID.IAudioEndpointVolume$ = UUID$("{5CDF2C82-841E-4546-9722-0CF74078229A}")
    CLSCTX.INPROC.SERVER = 1
    eRender = 0
    eConsole = 0
    Release = 2
    Activate = 3
    GetDefaultAudioEndpoint = 4
    GetMasterVolumeLevel = 8
    GetMasterVolumeLevelScalar = 9

    struct IMM, deviceEnumerator as ulong
    calldll #ole32, "CoCreateInstance", CLSID.MMDeviceEnumerator$ as ptr, _
        0 as long, CLSCTX.INPROC.SERVER as long, IID.MMDeviceEnumerator$ as ptr, _
        IMM as struct, hr as long

    ' hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
    struct default, Device as ulong
    struct parms, dataFlow as long, role as long, ppDevice as ptr
    parms.dataFlow.struct = eRender
    parms.role.struct = eConsole
    parms.ppDevice.struct = default.struct
    hr = CallMethod(IMM.deviceEnumerator.struct, GetDefaultAudioEndpoint, parms.struct)
    default.struct = parms.ppDevice.struct

    ' hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume),
    '      CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
    struct Iaudio, endpointVolume as ulong
    struct parms, iid as ptr, dwClsCtx as long, _
        pActivationParams as ulong, ppInterface as ptr
    parms.iid.struct = IID.IAudioEndpointVolume$
    parms.dwClsCtx.struct = CLSCTX.INPROC.SERVER
    parms.ppInterface.struct = Iaudio.struct
    hr = CallMethod(default.Device.struct, Activate, parms.struct)
    Iaudio.struct = parms.ppInterface.struct

    ' defaultDevice->Release();
    hr = CallMethod(default.Device.struct, Release, "")
    default.Device.struct = 0

    ' endpointVolume->GetMasterVolumeLevel(ĪtVolume);
    struct Volume, float as long
    struct parms, pfLevel as ptr
    parms.pfLevel.struct = Volume.struct
    hr = CallMethod(Iaudio.endpointVolume.struct, GetMasterVolumeLevel, parms.struct)
    Volume.struct = parms.pfLevel.struct

    ' Convert float to double:
    struct current, Volume as double
    float = Volume.float.struct
    calldll #oleaut32, "VarR8FromR4", float as long, current as struct, r as void

    ' hr = endpointVolume->GetMasterVolumeLevelScalar(ĪtVolume);
    hr = CallMethod(Iaudio.endpointVolume.struct, GetMasterVolumeLevelScalar, parms.struct)
    Volume.struct = parms.pfLevel.struct

    ' Convert float to double:
    float = Volume.float.struct
    calldll #oleaut32, "VarR8FromR4", float as long, current as struct, r as void
    GetVolume=int(current.Volume.struct*100+1)

    ' endpointVolume->Release();
    hr = CallMethod(Iaudio.endpointVolume.struct, Release, "")
    Iaudio.endpointVolume.struct = 0

    calldll #ole32, "CoUninitialize", r as void
    close #oleaut32
    close #ole32
end function

sub SetVolume nVolume, bScalar
    nVolume=nVolume/100
    open "ole32.dll" for dll as #ole32
    open "oleaut32.dll" for dll as #oleaut32
    calldll #ole32, "CoInitialize", 0 as long, r as void

    IID.MMDeviceEnumerator$ = UUID$("{A95664D2-9614-4F35-A746-DE8DB63617E6}")
    CLSID.MMDeviceEnumerator$ = UUID$("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
    IID.IAudioEndpointVolume$ = UUID$("{5CDF2C82-841E-4546-9722-0CF74078229A}")
    CLSCTX.INPROC.SERVER = 1
    eRender = 0
    eConsole = 0
    Release = 2
    Activate = 3
    GetDefaultAudioEndpoint = 4
    SetMasterVolumeLevelScalar = 7

    struct IMM, deviceEnumerator as ulong
    calldll #ole32, "CoCreateInstance", CLSID.MMDeviceEnumerator$ as ptr, _
        0 as long, CLSCTX.INPROC.SERVER as long, IID.MMDeviceEnumerator$ as ptr, _
        IMM as struct, hr as long

    ' hr = deviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &defaultDevice);
    struct default, Device as ulong
    struct parms, dataFlow as long, role as long, ppDevice as ptr
    parms.dataFlow.struct = eRender
    parms.role.struct = eConsole
    parms.ppDevice.struct = default.struct
    hr = CallMethod(IMM.deviceEnumerator.struct, GetDefaultAudioEndpoint, parms.struct)
    default.struct = parms.ppDevice.struct

    ' hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume),
    '      CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume);
    struct Iaudio, endpointVolume as ulong
    struct parms, iid as ptr, dwClsCtx as long, _
        pActivationParams as ulong, ppInterface as ptr
    parms.iid.struct = IID.IAudioEndpointVolume$
    parms.dwClsCtx.struct = CLSCTX.INPROC.SERVER
    parms.ppInterface.struct = Iaudio.struct
    hr = CallMethod(default.Device.struct, Activate, parms.struct)
    Iaudio.struct = parms.ppInterface.struct

    ' defaultDevice->Release();
    hr = CallMethod(default.Device.struct, Release, "")
    default.Device.struct = 0

    ' Set new volume:
    struct Volume, float as long
    struct parms, fLevel as ptr, EventContext as long
    calldll #oleaut32, "VarR4FromR8", nVolume as double, parms as struct, r as void
    hr = CallMethod(Iaudio.endpointVolume.struct, SetMasterVolumeLevelScalar, parms.struct)

    ' endpointVolume->Release();
    hr = CallMethod(Iaudio.endpointVolume.struct, Release, "")
    Iaudio.endpointVolume.struct = 0

    calldll #ole32, "CoUninitialize", r as void
    close #oleaut32
    close #ole32
end sub

function UUID$(iid$)
    l = len(iid$) + 1
    wide$ = space$(2 * l) + chr$(0)
    calldll #kernel32, "MultiByteToWideChar", 0 as long, 0 as long, _
                 iid$ as ptr, -1 as long, wide$ as ptr, l as long, r as long
    UUID$ = space$(16) + chr$(0)
    calldll #ole32, "CLSIDFromString", wide$ as ptr, UUID$ as ptr, r as long
end function

function CallMethod(object, method, parm$)
    code$ = chr$(139)+"D$"+chr$(4)+chr$(139)+"T$"+chr$(8)+chr$(139)+"L$" _
    + chr$(16)+"VW"+chr$(139)+"t$"+chr$(20)+chr$(43)+chr$(225)+chr$(139) _
    + chr$(252)+chr$(243)+chr$(164)+chr$(80)+chr$(139)+chr$(0)+chr$(255) _
    + chr$(20)+chr$(144)+chr$(95)+chr$(94)+chr$(194)+chr$(16)+chr$(0)
    p$ = parm$
    n = len(p$)
    calldll #user32, "CallWindowProcA", code$ as ptr, object as long,_
        method as long, p$ as ptr, n as long, CallMethod as long
end function

function GetTickCount()
    calldll #kernel32, "GetTickCount",_
        GetTickCount as ulong
end function 

Re: Get and set master sound level
Post by RobM on Oct 14th, 2016, 06:12am

The two loops in the code above can be replaced with this this. Makes it easier to insert into a program with a single loop ;)

Code:
[loop]
    scan
    calldll #kernel32, "Sleep", 10 as long, re as void
    CurrentTime=GetTickCount()
    CurrentVolume=GetVolume()
    if CurrentVolume>DesiredVolume and CountDownStarted=0 then
      StartTime=GetTickCount()
      CountDownStarted=1
    end if

    if CurrentTime-StartTime > ResetTime*60*1000 then
      call SetVolume DesiredVolume, 1
      CountDownStarted=0
    end if
    goto [loop]