Announcement

Collapse
No announcement yet.

Trying to port the C++ code for Microsoft sample of Peak Meter to PowerBASIC

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Trying to port the C++ code for Microsoft sample of Peak Meter to PowerBASIC

    I recently came across some Microsoft code of a Peak Meter program. Since it looked simple enough I decided to give it a try.

    Wouldn't you know that COM Object programming is needed!!! I just don't know much of it but I managed to port the code to PB10. It runs at 95% (or so I think). It compiles without errors but I can't make it to work as expected. I don't know how to test it to see if what I did is OK (assuming it compiles, it MAY be right).

    Could anyone please take a look at it to see if something could get fix?.
    There are two lines of code I couldn't get to work: one I just simply could not figure out how to port it, and the other does not seem to work properly because of the previous line. I marked them as this: "<===== HERE'S WHAT I CAN'T FIGURE OUT"

    I put the original C++ code and then the porting I did to PB10.
    The MS code can be found here: https://docs.microsoft.com/en-us/win...io/peak-meters


    Code:
    // Peakmeter.cpp -- WinMain and dialog box functions
    
    #include &amp;lt;windows.h&amp;gt;
    #include &amp;lt;mmdeviceapi.h&amp;gt;
    #include &amp;lt;endpointvolume.h&amp;gt;
    #include "resource.h"
    
    static BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
    static void DrawPeakMeter(HWND, float);
    
    // Timer ID and period (in milliseconds)
    #define ID_TIMER  1
    #define TIMER_PERIOD  125
    
    #define EXIT_ON_ERROR(hr)  \
                  if (FAILED(hr)) { goto Exit; }
    #define SAFE_RELEASE(punk)  \
                  if ((punk) != NULL)  \
                    { (punk)-&amp;gt;Release(); (punk) = NULL; }
    
    //-----------------------------------------------------------
    // WinMain -- Opens a dialog box that contains a peak meter.
    //   The peak meter displays the peak sample value that plays
    //   through the default rendering device.
    //-----------------------------------------------------------
    int APIENTRY WinMain(HINSTANCE hInstance,
                         HINSTANCE hPrevInstance,
                         LPSTR lpCmdLine,
                         int nCmdShow)
    {
        HRESULT hr;
        IMMDeviceEnumerator *pEnumerator = NULL;
        IMMDevice *pDevice = NULL;
        IAudioMeterInformation *pMeterInfo = NULL;
    
        if (hPrevInstance)
        {
            return 0;
        }
    
        CoInitialize(NULL);
    
        // Get enumerator for audio endpoint devices.
        hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                              NULL, CLSCTX_INPROC_SERVER,
                              __uuidof(IMMDeviceEnumerator),
                              (void**)&amp;amp;pEnumerator);
        EXIT_ON_ERROR(hr)
    
        // Get peak meter for default audio-rendering device.
        hr = pEnumerator-&amp;gt;GetDefaultAudioEndpoint(eRender, eConsole, &amp;amp;pDevice);
        EXIT_ON_ERROR(hr)
    
        hr = pDevice-&amp;gt;Activate(__uuidof(IAudioMeterInformation),
                               CLSCTX_ALL, NULL, (void**)&amp;amp;pMeterInfo);
        EXIT_ON_ERROR(hr)
    
        DialogBoxParam(hInstance, L"PEAKMETER", NULL, (DLGPROC)DlgProc, (LPARAM)pMeterInfo);
    
    Exit:
        if (FAILED(hr))
        {
            MessageBox(NULL, TEXT("This program requires Windows Vista."),
                       TEXT("Error termination"), MB_OK);
        }
        SAFE_RELEASE(pEnumerator)
        SAFE_RELEASE(pDevice)
        SAFE_RELEASE(pMeterInfo)
        CoUninitialize();
        return 0;
    }
    
    //-----------------------------------------------------------
    // DlgProc -- Dialog box procedure
    //-----------------------------------------------------------
    
    BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
        static IAudioMeterInformation *pMeterInfo = NULL;
        static HWND hPeakMeter = NULL;
        static float peak = 0;
        HRESULT hr;
    
        switch (message)
        {
        case WM_INITDIALOG:
            pMeterInfo = (IAudioMeterInformation*)lParam;
            SetTimer(hDlg, ID_TIMER, TIMER_PERIOD, NULL);
            hPeakMeter = GetDlgItem(hDlg, IDC_PEAK_METER);
            return TRUE;
    
        case WM_COMMAND:
            switch ((int)LOWORD(wParam))
            {
            case IDCANCEL:
                KillTimer(hDlg, ID_TIMER);
                EndDialog(hDlg, TRUE);
                return TRUE;
            }
            break;
    
        case WM_TIMER:
            switch ((int)wParam)
            {
            case ID_TIMER:
                // Update the peak meter in the dialog box.
                hr = pMeterInfo-&amp;gt;GetPeakValue(&amp;amp;peak);
                if (FAILED(hr))
                {
                    MessageBox(hDlg, TEXT("The program will exit."),
                               TEXT("Fatal error"), MB_OK);
                    KillTimer(hDlg, ID_TIMER);
                    EndDialog(hDlg, TRUE);
                    return TRUE;
                }
                DrawPeakMeter(hPeakMeter, peak);
                return TRUE;
            }
            break;
    
        case WM_PAINT:
            // Redraw the peak meter in the dialog box.
            ValidateRect(hPeakMeter, NULL);
            DrawPeakMeter(hPeakMeter, peak);
            break;
        }
        return FALSE;
    }
    
    //-----------------------------------------------------------
    // DrawPeakMeter -- Draws the peak meter in the dialog box.
    //-----------------------------------------------------------
    
    void DrawPeakMeter(HWND hPeakMeter, float peak)
    {
        HDC hdc;
        RECT rect;
    
        GetClientRect(hPeakMeter, &amp;amp;rect);
        hdc = GetDC(hPeakMeter);
        FillRect(hdc, &amp;amp;rect, (HBRUSH)(COLOR_3DSHADOW+1));
        rect.left++;
        rect.top++;
        rect.right = rect.left +
                     max(0, (LONG)(peak*(rect.right-rect.left)-1.5));
        rect.bottom--;
        FillRect(hdc, &amp;amp;rect, (HBRUSH)(COLOR_3DHIGHLIGHT+1));
        ReleaseDC(hPeakMeter, hdc);
    }
    Code:
    // Peakmeter.rc -- Resource script
    
    #include "resource.h"
    #include "windows.h"
    
    //
    // Dialog
    //
    PEAKMETER DIALOGEX 0, 0, 150, 34
    STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT
    CAPTION "Peak Meter"
    FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0
    BEGIN
        CTEXT      "",IDC_PEAK_METER,34,14,82,5
        LTEXT      "Min",IDC_STATIC_MINVOL,10,12,20,12
        RTEXT      "Max",IDC_STATIC_MAXVOL,120,12,20,12
    END
    
    
    
    
    
    // Resource.h -- Control identifiers
    
    #define IDC_STATIC_MINVOL      1001
    #define IDC_STATIC_MAXVOL      1002
    #define IDC_PEAK_METER         1003


    And here's the porting I made to PB10

    Code:
    ' PeakMeter
    ' From this Microsoft page:
    '    https://docs.microsoft.com/en-us/windows/desktop/coreaudio/peak-meters
    
    ' The following code example is a Windows application that displays a peak meter
    ' for the default rendering device:
    
    #COMPILE EXE
    #DIM ALL
    
    '#INCLUDE ONCE "WIN32API.INC"
    #INCLUDE "windows.inc"
    #INCLUDE "mmdeviceapi.inc"
    #INCLUDE "endpointvolume.inc"
    #RESOURCE "Microsoft Peak Meter.pbr"
    
    
    %IDC_STATIC_MINVOL = 1001
    %IDC_STATIC_MAXVOL = 1002
    %IDC_PEAK_METER    = 1003
    
    DECLARE FUNCTION DlgProc(BYVAL hWnd    AS DWORD, BYVAL wMsg    AS DWORD, BYVAL wParam  AS DWORD,_
                         BYVAL lParam  AS DWORD) AS LONG
    DECLARE SUB DrawPeakMeter(HWND AS DWORD, float AS SINGLE)
    
    '-----------------------------------------------------------------------------------------
    '#define EXIT_ON_ERROR(hr)  \
    '              if (FAILED(hr)) { goto Exit; }
    
    MACRO EXIT_ON_ERROR (hr) = IF FAILED(hr) GOTO done
    
    
    ' I couldn't get this one to work:
    
    '#define SAFE_RELEASE(punk)  \
    '              if ((punk) != NULL)  \
    '                { (punk)->Release(); (punk) = NULL; }
    
    'MACRO SAFE_RELEASE(punk)
    '  IF NOT punk = 0 THEN
    '    punk.Release()
    '    RESET punk
    '  END IF
    'END MACRO
    
    '-----------------------------------------------------------------------------------------
    
    
    '****************************
    ' **** MAIN ENTRY POINT ****
    '****************************
    
    FUNCTION WINMAIN ( _
         BYVAL  hInstance      AS DWORD, _
         BYVAL  hPrevInstance  AS DWORD, _
         BYVAL  lpszCmdLine    AS ASCIIZ PTR, _
         BYVAL  nCmdShow       AS LONG ) AS LONG
    
    '-----------------------------------------------------
         ' Timer ID and period (in milliseconds)
    DIM ID_TIMER     AS GLOBAL LONG
         DIM TIMER_PERIOD AS GLOBAL LONG
    
    ID_TIMER&     = 1
         TIMER_PERIOD& = 125
    '-----------------------------------------------------
    
         '-----------------------------------------------------
    DIM pEnumerator  AS IMMDeviceEnumerator
    DIM pDevice      AS IMMDevice
    DIM pMeterInfo   AS IAudioMeterInformation
    
    DIM hr         AS LOCAL LONG
         DIM punk       AS LOCAL LONG
    
    pEnumerator = NOTHING
    pDevice     = NOTHING
    pMeterInfo  = NOTHING
    '-----------------------------------------------------
    
    
         '-----------------------------------------------------
    IF hPrevInstance THEN EXIT FUNCTION
    '-----------------------------------------------------
    
    
    CoInitialize BYVAL %NULL
    
    '-----------------------------------------------------
         ' Get enumerator for audio endpoint devices.
    hr = CoCreateInstance($CLSID_MMDeviceEnumerator, NOTHING, %CLSCTX_INPROC_SERVER, $IID_IMMDeviceEnumerator, pEnumerator)
    
    EXIT_ON_ERROR (hr)
    '-----------------------------------------------------
    
         '-----------------------------------------------------
        ' Get peak meter for default audio-rendering device.
    hr = pEnumerator.GetDefaultAudioEndpoint(%EDataFlow_eRender, %DEVICE_STATE_ACTIVE, pDevice)
    ' Here we can use either %EDataFlow_eRender (output stream) or %EDataFlow_eCapture (input stream)
    
    EXIT_ON_ERROR (hr)
    
    hr = pDevice.Activate($IID_IAudioMeterInformation, %CLSCTX_ALL, $NUL, pMeterInfo)
    
    EXIT_ON_ERROR(hr)
    
    DialogBoxParam(hInstance, "PEAKMETER", %HWND_DESKTOP, CODEPTR(DlgProc), 0) ' hIcon
    
    
    done:
    IF FAILED(hr) THEN
    MessageBox(0, "This program requires Windows Vista.", "Error termination", %MB_OK)
    END IF
    
    'SAFE_RELEASE(pEnumerator)
         'SAFE_RELEASE(pDevice)
         'SAFE_RELEASE(pMeterInfo)
    pEnumerator = NOTHING
    pDevice = NOTHING
    pMeterInfo = NOTHING
    
    CoUninitialize()
    
    FUNCTION = hr
    ? "Program terminated"
    END FUNCTION
    
    
    '//-----------------------------------------------------------
    '// DlgProc -- Dialog box procedure
    '//-----------------------------------------------------------
    
    FUNCTION DlgProc(BYVAL hDlg    AS DWORD,_
                         BYVAL message AS DWORD,_
                         BYVAL wParam  AS DWORD,_
                         BYVAL lParam  AS DWORD) AS LONG
    
         DIM pMeterInfo AS STATIC IAudioMeterInformation
    DIM hPeakMeter AS STATIC DWORD
         DIM peak       AS STATIC SINGLE
         DIM hr         AS LOCAL LONG
    
    pMeterInfo = NOTHING
    
         SELECT CASE message
    CASE %WM_INITDIALOG
    '' pMeterInfo = (IAudioMeterInformation*)lparam;    <===== HERE'S WHAT I CAN'T FIGURE OUT  ****************
    SetTimer(hDlg, ID_TIMER&, TIMER_PERIOD, 0)
    hPeakMeter = GetDlgItem(hDlg, %IDC_PEAK_METER)
    'function = 1
    
    
    CASE %WM_COMMAND
                   SELECT CASE LOWORD(wParam)
    CASE %IDCANCEL
    KillTimer (hDlg, ID_TIMER&)
    EndDialog(hDlg, %TRUE)
    'function = 1
    END SELECT
    
    
              CASE %WM_TIMER
                   SELECT CASE wParam
    
    CASE ID_TIMER&
    '// Update the peak meter in the dialog box.
                            ' hr = pMeterInfo.GetPeakValue(peak)    '  <===== HERE'S WHAT I CAN'T FIGURE OUT   ***************
    
                            ' This two lines I used for testing only
    PEAK = PEAK + 0.1
    IF PEAK> 1 THEN PEAK = 0.2
    
    
    IF FAILED(hr) THEN
    MessageBox(hDlg, "The program will exit.","Fatal error", %MB_OK)
    KillTimer(hDlg, ID_TIMER&)
    EndDialog(hDlg, %TRUE)
    FUNCTION = 1
    EXIT SELECT
                            END IF
    DrawPeakMeter(hPeakMeter, peak)
    'function = 1
    
    
    END SELECT
    
              CASE %WM_PAINT
    
    '// Redraw the peak meter in the dialog box.
    ValidateRect(hPeakMeter, $NUL)
    DrawPeakMeter(hPeakMeter, peak)
    
    END SELECT
    
    'FUNCTION = 0
    
    
    
    END FUNCTION
    
    
    
    '//-----------------------------------------------------------
    '// DrawPeakMeter -- Draws the peak meter in the dialog box.
    '//-----------------------------------------------------------
    
    SUB DrawPeakMeter(hPeakMeter AS DWORD, peak AS SINGLE)
    
    ' Here I tinker a little to change the color of the bar according to its value.
        ' Nothing fancy
    
    
    
    DIM hdc   AS LOCAL DWORD
        DIM rectp AS LOCAL RECT
    
    DIM hWhiteBrush AS DWORD
        DIM hGreenBrush AS DWORD
        DIM hRedBrush AS DWORD
    
    hWhiteBrush =CreateSolidBrush(%RGB_WHITE)
    hGreenBrush =CreateSolidBrush(%RGB_GREEN)
    hRedBrush=CreateSolidBrush(%RGB_RED)
    
    
    GetClientRect(hPeakMeter, rectp)
    hdc = GetDC(hPeakMeter)
    FillRect(hdc, rectp, hWhiteBrush) ' < color of the entire empty bar
    INCR rectp.left
    INCR rectp.top
        rectp.right = rectp.left + MAX(0, (peak*(rectp.right-rectp.left)-1.5))
    DECR rectp.bottom
    
    IF peak > 0.7 THEN
    FillRect(hdc, rectp, hRedBrush)
    ELSE
    FillRect(hdc, rectp, hGreenBrush)
    END IF
    ReleaseDC(hPeakMeter, hdc)
    DeleteObject(hRedBrush)
    DeleteObject(hWhiteBrush)
    END SUB
    Francisco J Castanedo
    Software Developer
    Distribuidora 3HP, C.A.
    [URL]http://www.distribuidora3hp.com[/URL]

  • #2
    First, an in context note that some DDTers may not know:
    Transmission of DialogBoxParam() last parameter to lParam in WM_INITDIALOG is normal behavior in SDK code,
    but DDT does not give access to this functionality.

    So, in DialogBoxParam() the last parameter must be set with a pointer to pMeterInfo .
    This pointer will be available via lParam when in WM_INITDIALOG.
    Youll have to set pMeterInfo according to this.
    In WM_TIMER, with a valid pMeterInfo, GetPeakValue(Peak) should work well.

    Comment


    • #3
      Francisco,

      This is how I enumerate for a microphone. It should help...The code is very similar.
      Thanks to José

      Globals:
      Code:
      GLOBAL giMicrophone                         AS LONG   ' default microphone availablility  yes = 1, no = 0
      GLOBAL giMicrophoneHr                       AS LONG
      GLOBAL gpIMMDeviceEnumMicrophone            AS IMMDeviceEnumerator
      GLOBAL gpIMMDeviceMicrophone                AS IMMDevice
      GLOBAL gpIAudioEndpointVolumeMicrophone     AS IAudioEndpointVolume
      GLOBAL giMasterMicrophoneLevel              AS LONG
      GLOBAL gsTheMicrophoneName                  AS STRING
      Code:
      '_________________________________________________________________
      '
      '   FUNCTION  MicrophoneCheck
      '_________________________________________________________________
      
      FUNCTION MicrophoneCheck AS LONG
          LOCAL xhr     AS LONG
          LOCAL pDevId  AS WSTRINGZ PTR
          LOCAL devName AS WSTRING
      
          gpIMMDeviceEnumMicrophone = NEWCOM CLSID $CLSID_MMDeviceEnumerator
          IF ISNOTHING(gpIMMDeviceEnumMicrophone) THEN EXIT FUNCTION
      
          ' // Enumerate the audio endpoints of interest (in this case audio capture endpoints)
          ' // The flags that can be used are:
          ' // %EDataFlow_eRender (for audio output endpoints),
          ' // %EDataFlow_eCapture (for audio capture endpoints),
          ' // %EDataFlow_eAll (for all audio endpoints)
      
          ' // %ERole_eConsole = Games, system notification sounds, and voice commands.
          ' // %ERole_eMultimedia = Music, movies, narration, and live music recording.
          ' // %ERole_eCommunications = Voice communications (talking to another person).
      
          giMicrophoneHr = gpIMMDeviceEnumMicrophone.GetDefaultAudioEndpoint(%EDataFlow_eCapture, %ERole_eConsole, gpIMMDeviceMicrophone)
          IF FAILED(giMicrophoneHr) THEN
              'CALL SHOW_NOTES("Unable to find default microphone.")
              CONTROL SET TEXT hWndMain, %ID_IDNOTE, "Unable to find default microphone."
              ' "Unable to find the default microphone"
              FUNCTION = 0
              EXIT FUNCTION
          END IF
      
          xhr = gpIMMDeviceMicrophone.Activate($IID_IAudioEndpointVolume, %CLSCTX_INPROC_SERVER, BYVAL %NULL, gpIAudioEndpointVolumeMicrophone)
          IF FAILED(xhr) THEN
              'CALL SHOW_NOTES("Unable to activate a microphone.")
              CONTROL SET TEXT hWndMain, %ID_IDNOTE, "Unable to activate a microphone."
              ' "Unable to activate default microphone"
              FUNCTION = 0
              EXIT FUNCTION
          ELSE
              ' "Default microphone activated successfully!"
      
              ' // Get the device identifier
              xhr = gpIMMDeviceMicrophone.GetId(pDevId)
              IF pDevId THEN
                  ' "Device id = " & @pDevId
                  CoTaskMemFree(pDevId)
              END IF
              ' // Get the device name
              gsTheMicrophoneName = AfxGetDeviceName(gpIMMDeviceMicrophone)
      
              SetVolumeMicrophone(0.2)
          END IF
      
          FUNCTION = 1
      END FUNCTION

      This is not needed:
      Code:
      ' I couldn't get this one to work:
      
      '#define SAFE_RELEASE(punk)  \
      '              if ((punk) != NULL)  \
      '                { (punk)->Release(); (punk) = NULL; }
      
      'MACRO SAFE_RELEASE(punk)
      '  IF NOT punk = 0 THEN
      '    punk.Release()
      '    RESET punk
      '  END IF
      'END MACRO
      These are what you do for the SAFE_RELEASE (when you no longer need them.)

      Code:
       
      pEnumerator = NOTHING pDevice = NOTHING pMeterInfo = NOTHING

      Comment


      • #4
        Hi Francisco,

        The pMeterInfo is referencing an ObjPtr of IAudioMeterInformation

        Lines to change just on the top of my head. Forgive me if I am mistaken.

        1.

        DIM pMeterInfo AS STATIC IAudioMeterInformation

        into

        DIM pMeterInfo AS STATIC IAudioMeterInformation Ptr

        2.

        CASE %WM_INITDIALOG
        '' pMeterInfo = (IAudioMeterInformation*)lparam; <===== HERE'S WHAT I CAN'T FIGURE OUT ****************

        should be

        CASE %WM_INITDIALOG
        pMeterInfo = lParam

        3.
        CASE ID_TIMER&
        '// Update the peak meter in the dialog box.
        '
        should be

        CASE ID_TIMER&
        '// Update the peak meter in the dialog box.
        hr = @pMeterInfo.GetPeakValue(peak) ' <===== HERE'S WHAT I CAN'T FIGURE OUT ***************

        EDIT:

        I forgot whether it is possible to set the interface as a pointer in PowerBasic. (Long time ago).

        You may have to do something like

        DIM pMeterInfo AS STATIC IAudioMeterInformation

        Poke Dword, ObjPtr(pMeterInfo), lParam





        So here we are, this is the end.
        But all that dies, is born again.
        - From The Ashes (In This Moment)

        Comment


        • #5
          Help DIM shows that way too! Help for STATIC does not.
          Cheers,
          Dale

          Comment


          • #6
            Originally posted by Pierre Bellisle View Post
            First, an in context note that some DDTers may not know:
            Transmission of DialogBoxParam() last parameter to lParam in WM_INITDIALOG is normal behavior in SDK code,
            but DDT does not give access to this functionality.

            So, in DialogBoxParam() the last parameter must be set with a pointer to pMeterInfo .
            This pointer will be available via lParam when in WM_INITDIALOG.
            Youll have to set pMeterInfo according to this.
            In WM_TIMER, with a valid pMeterInfo, GetPeakValue(Peak) should work well.
            Hi Pierre, following your suggestion I did reference the last parameter of DialogBoxParam() to pMeterInfo like this:
            Code:
            DialogBoxParam(hInstance, "PEAKMETER", %HWND_DESKTOP, CODEPTR(DlgProc), VARPTR(pMeterInfo))
            But I am not sure what it's being referenced, either the value of pMeterInfo or any other pointer. I say this because pMeterInfo is not a typical BASIC variable like a LONG, a SINGLE, or a STRING.

            Anyway, the compiler accepted the value. But my problem is that I don't know how to retrieve that value from lparam. When I try pMeterInfo = lparam, i get this compiler warning: "Object variable expected".
            If I do either of this: pMeterInfo = VARPTR(lparam), pMeterInfo = STRPTR(lparam) or pMeterInfo = PEEK(lparam) I get a "Variable expected error".

            Also, I may be mistaken but lparam is a DWORD whereas pMeterInfo simess more as a poi8nter to something in IAudioMeterInformation and since I'm mixing COM objects with regular variables it should not work.

            Bottom line is I don't have a clue on how to make this work and pass the value of pMeterInfo into the callback.

            Since the expression in C++ pMeterInfo = (IAudioMeterInformation*)lparam seems simple enough to translate to PowerBASIC, any help in this regard will sure help a lot.

            And yes, I'm a DDTer that mixes DDT with Winows API (they work like a charm) and perhaps some assembler but I'm a complete illiterate on COM Object programming.

            Thanks for your input, though.
            Francisco J Castanedo
            Software Developer
            Distribuidora 3HP, C.A.
            [URL]http://www.distribuidora3hp.com[/URL]

            Comment


            • #7
              Originally posted by Steven Pringels 3 View Post
              Hi Francisco,

              The pMeterInfo is referencing an ObjPtr of IAudioMeterInformation

              Lines to change just on the top of my head. Forgive me if I am mistaken.

              1. DIM pMeterInfo AS STATIC IAudioMeterInformation Ptr

              2.CASE %WM_INITDIALOG
              pMeterInfo = lParam

              3. hr = @pMeterInfo.GetPeakValue(peak) ' <===== HERE'S WHAT I CAN'T FIGURE OUT ***************



              Hi Steven, I tried what you suggest and found that

              1. This does not work as expected and haven't seen any example that uses the PTR phrase in the declaration.

              2. pMeterInfo = lParam - The compiler won't allow this at all. They are different type of variables.

              3. hr = @pMeterInfo.GetPeakValue(peak) does not work. The correct way is
              hr = pMeterInfo.GetPeakValue(peak) but this is not working at all and may be because the value referenced is nonsense and windows just gives what used to be known as a BSoD but with good behaviour (the program just crashes)

              Thanks a lot. I tried all of this but top no avail.
              Francisco J Castanedo
              Software Developer
              Distribuidora 3HP, C.A.
              [URL]http://www.distribuidora3hp.com[/URL]

              Comment


              • #8
                I guess it could look like this, use OBJPTR()...

                Code:
                DialogBoxParam(hInstance, "PEAKMETER", %HWND_DESKTOP, CODEPTR(DlgProc), OBJPTR(pMeterInfo))
                
                FUNCTION DlgProc(...
                
                   CASE %WM_INITDIALOG
                
                     pMeterInfo = NOTHING
                     POKE DWORD, VARPTR(pMeterInfo), lParam
                
                     SetTimer(hDlg, %ID_TIMER, %TIMER_PERIOD, %NULL)
                     hPeakMeter = GetDlgItem(hDlg, %IDC_PEAK_METER)
                     FUNCTION = %TRUE
                     EXIT FUNCTION
                
                   ...
                
                   CASE %WM_TIMER
                      SELECT CASE wParam
                        CASE %ID_TIMER
                          hr = pMeterInfo.GetPeakValue(Peak)
                          ...

                Comment


                • #9
                  Hi Jim Fritts, thanks for the SAFE_RELEASE thing. I figured that much but I wasn't really sure. I will check your code and see if something there helps.

                  And Dale, I'm sure you meant soimething good there but I just don't get it. It is strenge enough to find out that DIM Variable as LONG PTR is complete different to a DIM pVariable as IMMWhatevermethod.!!!
                  Francisco J Castanedo
                  Software Developer
                  Distribuidora 3HP, C.A.
                  [URL]http://www.distribuidora3hp.com[/URL]

                  Comment


                  • #10
                    Thanks Pierre, it works fine!. That was it. I'll clean the code and put a finished version here.

                    I just found that when I close the program, it remains running for many seconds. I have to wait until it finally unloads from memory. Any ideas?

                    Added: It remains in memory for about 34 secs.
                    Francisco J Castanedo
                    Software Developer
                    Distribuidora 3HP, C.A.
                    [URL]http://www.distribuidora3hp.com[/URL]

                    Comment


                    • #11
                      Heres the final code:

                      Code:
                      ' PeakMeter
                      ' From this Microsoft page:
                      '    https://docs.microsoft.com/en-us/windows/desktop/coreaudio/peak-meters
                      
                      ' The following code example is a Windows application that displays a peak meter
                      ' for the default rendering device:
                      
                      #COMPILE EXE
                      #DIM ALL
                      
                      #INCLUDE "windows.inc"
                      #INCLUDE "mmdeviceapi.inc"
                      #INCLUDE "endpointvolume.inc"
                      #RESOURCE "Microsoft Peak Meter.pbr"
                      
                      
                      %IDC_STATIC_MINVOL = 1001
                      %IDC_STATIC_MAXVOL = 1002
                      %IDC_PEAK_METER    = 1003
                      
                      DECLARE FUNCTION DlgProc(BYVAL hWnd    AS DWORD, BYVAL wMsg    AS DWORD, BYVAL wParam  AS DWORD,_
                                           BYVAL lParam  AS DWORD) AS LONG
                      DECLARE SUB DrawPeakMeter(HWND AS DWORD, float AS SINGLE)
                      
                      MACRO EXIT_ON_ERROR (hr) = IF FAILED(hr) GOTO done
                      
                      '-----------------------------------------------------------------------------------------
                      
                      
                      '****************************
                      ' **** MAIN ENTRY POINT ****
                      '****************************
                      
                      FUNCTION WINMAIN ( _
                           BYVAL  hInstance      AS DWORD, _
                           BYVAL  hPrevInstance  AS DWORD, _
                           BYVAL  lpszCmdLine    AS ASCIIZ PTR, _
                           BYVAL  nCmdShow       AS LONG ) AS LONG
                      
                           '-----------------------------------------------------
                           ' Timer ID and period (in milliseconds)
                           DIM ID_TIMER     AS GLOBAL LONG
                           DIM TIMER_PERIOD AS GLOBAL LONG
                      
                           ID_TIMER&     = 1
                           TIMER_PERIOD& = 125
                           '-----------------------------------------------------
                      
                           '-----------------------------------------------------
                           DIM pEnumerator  AS IMMDeviceEnumerator
                           DIM pDevice      AS IMMDevice
                           DIM pMeterInfo   AS IAudioMeterInformation
                      
                           DIM hr         AS LOCAL LONG
                           DIM punk       AS LOCAL LONG
                      
                           pEnumerator = NOTHING
                           pDevice     = NOTHING
                           pMeterInfo  = NOTHING
                           '-----------------------------------------------------
                      
                           '-----------------------------------------------------
                           IF hPrevInstance THEN EXIT FUNCTION
                           '-----------------------------------------------------
                      
                           CoInitialize BYVAL %NULL
                      
                           '-----------------------------------------------------
                           ' Get enumerator for audio endpoint devices.
                           hr = CoCreateInstance($CLSID_MMDeviceEnumerator, NOTHING, %CLSCTX_INPROC_SERVER, $IID_IMMDeviceEnumerator, pEnumerator)
                           EXIT_ON_ERROR (hr)
                           '-----------------------------------------------------
                      
                           '-----------------------------------------------------
                          ' Get peak meter for default audio-rendering device.
                            hr = pEnumerator.GetDefaultAudioEndpoint(%EDataFlow_eRender, %DEVICE_STATE_ACTIVE, pDevice)
                            ' Here we can use either %EDataFlow_eRender (output stream) or %EDataFlow_eCapture (input stream)
                           EXIT_ON_ERROR (hr)
                      
                           hr = pDevice.Activate($IID_IAudioMeterInformation, %CLSCTX_ALL, $NUL, pMeterInfo)
                           EXIT_ON_ERROR(hr)
                      
                           DialogBoxParam(hInstance, "PEAKMETER", %HWND_DESKTOP, CODEPTR(DlgProc), OBJPTR(pMeterInfo))
                      
                      done:
                           IF FAILED(hr) THEN
                               MessageBox(0, "This program requires Windows Vista.", "Error termination", %MB_OK)
                           END IF
                      
                            pEnumerator = NOTHING
                            pDevice = NOTHING
                            pMeterInfo = NOTHING
                      
                           CoUninitialize()
                      
                           FUNCTION = hr
                           ? "Program terminated"
                      END FUNCTION
                      
                      
                      '//-----------------------------------------------------------
                      '// DlgProc -- Dialog box procedure
                      '//-----------------------------------------------------------
                      
                      FUNCTION DlgProc(BYVAL hDlg    AS DWORD,_
                                           BYVAL message AS DWORD,_
                                           BYVAL wParam  AS DWORD,_
                                           BYVAL lParam  AS DWORD) AS LONG
                      
                           DIM pMeterInfo AS STATIC IAudioMeterInformation
                           DIM hPeakMeter AS STATIC DWORD
                           DIM peak       AS STATIC SINGLE
                           DIM hr         AS LOCAL LONG
                      
                           SELECT CASE message
                                CASE %WM_INITDIALOG
                      
                                   ' Initialize pMeterInfo and save the correct value in it (Thanks to Pierre BelLisle)
                                   pMeterInfo = NOTHING
                                   POKE DWORD, VARPTR(pMeterInfo), lParam
                      
                                   SetTimer(hDlg, ID_TIMER&, TIMER_PERIOD, 0)
                                   hPeakMeter = GetDlgItem(hDlg, %IDC_PEAK_METER)
                                   FUNCTION = %TRUE
                      
                      
                                CASE %WM_COMMAND
                                     SELECT CASE LOWORD(wParam)
                                          CASE %IDCANCEL
                                               KillTimer (hDlg, ID_TIMER&)
                                               EndDialog(hDlg, %TRUE)
                                               FUNCTION = %TRUE
                                     END SELECT
                      
                      
                                CASE %WM_TIMER
                                     SELECT CASE wParam
                      
                                          CASE ID_TIMER&
                                              '// Update the peak meter in the dialog box.
                                              hr = pMeterInfo.GetPeakValue(peak)
                      
                                              IF FAILED(hr) THEN
                                                  MessageBox(hDlg, "The program will exit.","Fatal error", %MB_OK)
                                                  KillTimer(hDlg, ID_TIMER&)
                                                  EndDialog(hDlg, %TRUE)
                                                  FUNCTION = 0
                                                  EXIT SELECT
                                              END IF
                                              DrawPeakMeter(hPeakMeter, peak)
                                              FUNCTION = %TRUE
                                     END SELECT
                      
                                CASE %WM_PAINT
                                     '// Redraw the peak meter in the dialog box.
                                     ValidateRect(hPeakMeter, $NUL)
                                     DrawPeakMeter(hPeakMeter, peak)
                      
                           END SELECT
                      
                           FUNCTION = 0
                      
                      END FUNCTION
                      
                      
                      
                      '//-----------------------------------------------------------
                      '// DrawPeakMeter -- Draws the peak meter in the dialog box.
                      '//-----------------------------------------------------------
                      
                      SUB DrawPeakMeter(hPeakMeter AS DWORD, peak AS SINGLE)
                      
                          DIM hdc   AS LOCAL DWORD
                          DIM rectp AS LOCAL RECT
                      
                          DIM hWhiteBrush AS DWORD
                          DIM hGreenBrush AS DWORD
                      
                          hWhiteBrush =CreateSolidBrush(%RGB_WHITE)
                          hGreenBrush =CreateSolidBrush(%RGB_GREEN)
                      
                          GetClientRect(hPeakMeter, rectp)
                          hdc = GetDC(hPeakMeter)
                      
                          FillRect(hdc, rectp, hWhiteBrush) ' < color of the entire empty bar
                      
                          INCR rectp.left
                          INCR rectp.top
                          rectp.right = rectp.left + MAX(0, (peak*(rectp.right-rectp.left)-1.5))
                          DECR rectp.bottom
                      
                          FillRect(hdc, rectp, hGreenBrush)
                      
                          ReleaseDC(hPeakMeter, hdc)
                          DeleteObject(hWhiteBrush)
                          DeleteObject(hGreenBrush)
                      END SUB
                      Code:
                      // Microsoft Peak Meter.rc
                      
                      //PeakMeter.rc -- Resource script
                      
                      #include "resource.h"
                      
                      // Resource.h -- Control identifiers
                      
                      #define IDC_STATIC_MINVOL      1001
                      #define IDC_STATIC_MAXVOL      1002
                      #define IDC_PEAK_METER         1003
                      
                      //
                      // Dialog
                      //
                      PEAKMETER DIALOGEX 0, 0, 150, 34
                      STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
                      CAPTION "Peak Meter"
                      FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0
                      BEGIN
                          CTEXT      "",IDC_PEAK_METER,34,14,82,5
                          LTEXT      "Min",IDC_STATIC_MINVOL,10,12,20,12
                          RTEXT      "Max",IDC_STATIC_MAXVOL,120,12,20,12
                      END
                      Francisco J Castanedo
                      Software Developer
                      Distribuidora 3HP, C.A.
                      [URL]http://www.distribuidora3hp.com[/URL]

                      Comment


                      • #12
                        I just found that when I close the program, it remains running for many seconds. ... Any ideas?

                        Looks like, it come from the pMeterInfo in DlgProc not beeing treated correctly on program exit.
                        If I add WM_DESTROY with a "POKE DWORD, VARPTR(pMeterInfo), 0" to reset it, then I got no problem.
                        There is probably a more elegant way to do this, I will tell if I find something better.

                        Also, an easy solution could be to use only one global pMeterInfo.

                        Comment


                        • #13
                          Francisco,
                          Here is an SDK version that outputs to a progress bar. Have not noticed any lingering.

                          Click image for larger version

Name:	PeakMeter.jpg
Views:	1
Size:	6.0 KB
ID:	776707

                          Code:
                          ' PeakMeter via progress bar SDK version
                          ' From this Microsoft page:
                          '    https://docs.microsoft.com/en-us/windows/desktop/coreaudio/peak-meters
                          
                          ' The following code example is a Windows application that displays a peak meter
                          ' for the default rendering device:
                          
                          'Works with PBWIN 10.4 and Jose' includes
                          
                          #COMPILE EXE "PeakMeter.exe"
                          #DIM ALL
                          
                          #INCLUDE ONCE "WIN32API.INC"
                          #INCLUDE "mmdeviceapi.inc"
                          #INCLUDE "endpointvolume.inc"
                          
                          
                          %IDC_STATIC_MINVOL = 1001
                          %IDC_STATIC_MAXVOL = 1002
                          %IDC_PEAK_METER    = 1003
                          %IDC_Timer0        = 1004
                          %ID_VOLUME         = 1005
                          
                          
                          GLOBAL hWndMain              AS DWORD
                          GLOBAL pEnumerator           AS IMMDeviceEnumerator
                          GLOBAL pDevice               AS IMMDevice
                          GLOBAL pMeterInfo            AS IAudioMeterInformation
                          GLOBAL dwNoRetMLTextBoxStyle AS DWORD
                          
                          GLOBAL hPeakMeter            AS DWORD
                          GLOBAL PEAK                  AS SINGLE
                          GLOBAL dwSProgressStyle      AS DWORD
                          GLOBAL dwSNBProgressStyle    AS DWORD
                          GLOBAL dwProgressStyle       AS DWORD
                          GLOBAL dwNBProgressStyle     AS DWORD
                          
                          
                          DECLARE SUB DrawPeakMeter(HWND AS DWORD, float AS SINGLE)
                          
                          
                          '****************************
                          ' **** MAIN ENTRY POINT ****
                          '****************************
                          
                          FUNCTION WINMAIN (BYVAL hInstance AS DWORD, _
                                            BYVAL hPrevInstance AS DWORD, _
                                            BYVAL lpCmdLine AS ASCIIZ PTR, _
                                            BYVAL iCmdShow AS LONG) AS LONG
                          
                              LOCAL Msg          AS tagMsg
                              LOCAL W            AS WndClassEx
                              LOCAL szAppName    AS ASCIIZ * 80
                              LOCAL szClassName  AS ASCIIZ * 80
                              LOCAL WndStyle     AS LONG
                              LOCAL WndStyleX    AS LONG
                          
                              'register window class
                              szAppName = "PeakMeter"
                              szClassName = "#32770"
                          
                              W.cbSize        = SIZEOF(W)
                              W.Style         = %CS_HREDRAW OR %CS_VREDRAW   '%WS_OverlappedWindow Or %WS_HScroll Or %WS_VScroll  '
                              W.lpfnWndProc   = CODEPTR(WndProc)
                              W.cbClsExtra    = 0
                              W.cbWndExtra    = 0
                              W.hInstance     = hInstance
                              W.hIcon         = 0 'LoadImage(hInstance, BYVAL 3000, %IMAGE_ICON, 0, 0, %LR_LOADMAP3DCOLORS OR %LR_DEFAULTSIZE)
                              W.hCursor       = LoadCursor(%NULL, BYVAL %IDC_ARROW)
                              W.hbrBackground = GetStockObject(%DKGRAY_BRUSH) '(%LTGRAY_BRUSH) '%COLOR_3DDKSHADOW 'COLOR_BTNSHADOW'%Color_BtnFace+1 (%COLOR_ACTIVEBORDER)
                              W.lpszMenuName  = %NULL
                              W.lpszClassName = VARPTR(szClassName)
                              W.hIconSm       = W.hIcon
                              RegisterClassEx W
                          
                          
                              'create window of that class
                              WndStyle  = %WS_VISIBLE OR %DS_MODALFRAME OR %WS_CAPTION OR %WS_SYSMENU OR %DS_SETFONT OR %DS_NOFAILCREATE 'OR %WS_POPUP OR %WS_BORDER OR %DS_MODALFRAME
                              WndStyleX = %WS_EX_LEFT OR %WS_EX_WINDOWEDGE OR %WS_EX_APPWINDOW
                          
                              hWndMain = CreateWindowEx( _
                                  WndStyleX,     _  ' extended styles
                                  szClassName,   _  ' window class name
                                  szAppName,     _  ' caption
                                  WndStyle,      _  ' window styles
                                  300,           _  ' left
                                  300,           _  ' top
                                  232,           _  ' width
                                  95,            _  ' height
                                  %HWND_DESKTOP, _  ' parent window handle
                                  %NULL,         _  ' window menu handle
                                  hInstance,     _  ' program instance handle
                                  BYVAL %NULL)      ' creation parameters
                          
                              CALL CreateDlgControls
                          
                          
                              ShowWindow hWndMain, iCmdShow   'controls how window is to be shown. 1st must use iCmdShow
                              UpdateWindow hWndMain           'sends the window it's first WM_Create to display the window on the screen
                          
                              'message pump - calls WndProc whenever an application-specific message is received
                              'WndProc can process the message, or pass it on to a the default window procedure that is built into Windows.
                              WHILE GetMessage(Msg, %NULL, 0, 0) > 0
                                IF ISFALSE ISDialogMessage (hWndMain, Msg) THEN
                                   TranslateMessage Msg
                                   DispatchMessage  Msg
                                END IF
                              WEND
                          END FUNCTION
                          
                          
                          '//-----------------------------------------------------------
                          '// DlgProc -- Dialog box procedure
                          '//-----------------------------------------------------------
                          
                          FUNCTION WndProc (BYVAL hWnd AS DWORD, _
                                            BYVAL wMsg AS DWORD, _
                                            BYVAL wParam AS DWORD, _
                                            BYVAL lParam AS LONG) EXPORT AS LONG
                          
                              LOCAL xhr      AS LONG
                          
                              '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                              'Create a timer event every 1/8 Sec = 125
                              '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                              SetTimer hWndMain, %IDC_Timer0, 125, BYVAL %NULL
                          
                              SELECT CASE wMsg
                          
                                  CASE %WM_CREATE
                                      pMeterInfo = NOTHING
                          
                                      ' Get enumerator for audio endpoint devices.
                                      pEnumerator = NEWCOM CLSID $CLSID_MMDeviceEnumerator
                                      IF ISNOTHING(pEnumerator) THEN
                                          ? "Unable to create audio device enumerator."
                                          FUNCTION = 0
                                          EXIT FUNCTION
                                      END IF
                          
                                      ' // Enumerate the audio endpoints of interest (in this case audio capture endpoints)
                                      ' // The flags that can be used are:
                                      ' // %EDataFlow_eRender (for audio output endpoints),
                                      ' // %EDataFlow_eCapture (for audio capture endpoints),
                                      ' // %EDataFlow_eAll (for all audio endpoints)
                          
                                      ' // %ERole_eConsole = Games, system notification sounds, and voice commands.
                                      ' // %ERole_eMultimedia = Music, movies, narration, and live music recording.
                                      ' // %ERole_eCommunications = Voice communications (talking to another person).
                          
                                      xhr = pEnumerator.GetDefaultAudioEndpoint(%EDataFlow_eRender, %DEVICE_STATE_ACTIVE, pDevice)
                                      IF FAILED(xhr) THEN
                                          ? "Unable to find default audio device."
                                          FUNCTION = 0
                                          EXIT FUNCTION
                                      END IF
                          
                                      xhr = pDevice.Activate($IID_IAudioMeterInformation, %CLSCTX_ALL, BYVAL %NULL, pMeterInfo)
                                      IF FAILED(xhr) THEN
                                          ? "Unable to get peak meter info."
                                          FUNCTION = 0
                                          EXIT FUNCTION
                                      END IF
                          
                                      'If an application processes this message, it should return zero to continue creation of the window.
                                      FUNCTION = 0
                                      EXIT FUNCTION
                          
                          
                          
                                  CASE %WM_COMMAND
                                      SELECT CASE LOWORD(wParam)
                                          CASE %IDCANCEL
                                              pEnumerator = NOTHING
                                              pDevice = NOTHING
                                              pMeterInfo = NOTHING
                                              KillTimer (hWnd, %IDC_Timer0)
                                              EndDialog(hWnd, %TRUE)
                                              'function = 1
                                      END SELECT
                                      'If an application processes this message, it should return zero.
                                      FUNCTION = 0
                                      EXIT FUNCTION
                          
                                  CASE %WM_TIMER
                                     SELECT CASE wParam
                                         CASE %IDC_Timer0
                                             '// Update the peak meter in the dialog box.
                                             IF ISOBJECT(pMeterInfo) THEN
                                                 pMeterInfo.GetPeakValue(PEAK)
                                                 CONTROL SET TEXT hWndMain, %IDC_PEAK_METER, STR$(PEAK)
                                                 CONTROL SEND hWndMain, %ID_VOLUME, %PBM_SETPOS, PEAK * 100, 0
                                             END IF
                          
                                     END SELECT
                                     'An application should return zero if it processes this message.
                                     FUNCTION = 0
                                     EXIT FUNCTION
                          
                                  CASE %WM_CLOSE
                                      DestroyWindow hWnd
                          
                                  CASE %WM_DESTROY         'window is being destroyed
                                                           'after windows is removed from screen (children still exist)
                                      PostQuitMessage 0
                                      'If an application processes this message, it should return zero.
                                      FUNCTION = 0
                                      EXIT FUNCTION
                          
                              END SELECT
                          
                          
                              FUNCTION = DefWindowProc(hWnd, wMsg, wParam, lParam)   'if not handled above, pass to Windows default message handler.
                          END FUNCTION
                          
                          SUB CreateDLGControls
                            LOCAL MyCtrl   AS DWORD
                            dwNoRetMLTextBoxStyle  = %WS_CHILD OR %WS_VISIBLE OR %ES_NOHIDESEL OR %ES_READONLY OR %WS_BORDER
                            dwSProgressStyle       = %WS_CHILD OR %WS_VISIBLE OR %PBS_SMOOTH OR %WS_BORDER '%PBS_SMOOTH for undotted bar
                            dwSNBProgressStyle     = %WS_CHILD OR %WS_VISIBLE OR %PBS_SMOOTH
                            dwProgressStyle        = %WS_CHILD OR %WS_VISIBLE OR %WS_BORDER
                            dwNBProgressStyle      = %WS_CHILD OR %WS_VISIBLE
                          
                          
                            MyCtrl = addTextBox(hWndMain, %IDC_STATIC_MINVOL, "", 10, 12, 20, 24, dwNoRetMLTextBoxStyle OR %SS_LEFT)
                            MyCtrl = addTextBox(hWndMain, %IDC_PEAK_METER, "", 31, 12, 164, 24, dwNoRetMLTextBoxStyle OR %SS_CENTER)
                            MyCtrl = addTextBox(hWndMain, %IDC_STATIC_MAXVOL, "", 196, 12, 20, 24, dwNoRetMLTextBoxStyle OR %SS_RIGHT)
                          
                            MyCtrl = addProgress(hWndMain, %ID_VOLUME, "", 10, 40, 205, 12, dwSProgressStyle)
                            CONTROL SEND hWndMain, %ID_VOLUME, %PBM_SETRANGE, 0, MAKLNG(0, 100)
                            CONTROL SEND hWndMain, %ID_VOLUME, %PBM_SETSTEP, -1, 0
                            CONTROL SEND hWndMain, %ID_VOLUME, %PBM_SETBKCOLOR, 0, RGB(50,50,50)
                            CONTROL SEND hWndMain, %ID_VOLUME, %PBM_SETBARCOLOR, 0, RGB(55,224,230)
                          
                          
                          END SUB
                          
                          FUNCTION addTextBox(BYVAL hParent AS DWORD, BYVAL nID AS LONG, zCaption AS ASCIIZ, BYVAL x AS LONG, BYVAL y AS LONG, BYVAL w AS LONG, BYVAL h AS LONG, BYVAL dwStyle AS DWORD) AS DWORD
                              LOCAL hCtrl AS DWORD
                              LOCAL hInstance AS DWORD
                          
                              hInstance = GetWindowLong(hWndMain, %GWL_HINSTANCE)
                          
                              hCtrl = CreateWindowEx(%WS_EX_CLIENTEDGE, "Edit", zCaption, dwStyle, x, y, w, h, hParent, nID, hInstance, BYVAL %NULL)
                              'DisableXPThemeControl hCtrl
                              'IF hCtrl THEN SendMessage(hCtrl, %WM_SETFONT, gP.usefont, 0)
                              FUNCTION = hCtrl
                          END FUNCTION
                          
                          
                          FUNCTION addProgress(BYVAL hParent AS DWORD, BYVAL nID AS LONG, zCaption AS ASCIIZ, BYVAL x AS LONG, BYVAL y AS LONG, BYVAL w AS LONG, BYVAL h AS LONG, BYVAL dwStyle AS DWORD) AS DWORD
                              LOCAL hCtrl AS DWORD
                              LOCAL hInstance AS DWORD
                          
                              hInstance = GetWindowLong(hWndMain, %GWL_HINSTANCE)
                          
                              hCtrl = CreateWindowEx(0, "MsCtls_Progress32", zCaption, dwStyle, x, y, w, h, hParent, nID, hInstance, BYVAL %NULL)
                              'DisableXPThemeControl hCtrl
                              'IF hCtrl THEN SendMessage(hCtrl, %WM_SETFONT, gP.usefont, 0)    '%WS_EX_TRANSPARENT
                              FUNCTION = hCtrl
                          END FUNCTION

                          Comment


                          • #14
                            Originally posted by Pierre Bellisle View Post
                            I just found that when I close the program, it remains running for many seconds. ... Any ideas?

                            Looks like, it come from the pMeterInfo in DlgProc not beeing treated correctly on program exit.
                            If I add WM_DESTROY with a "POKE DWORD, VARPTR(pMeterInfo), 0" to reset it, then I got no problem.
                            There is probably a more elegant way to do this, I will tell if I find something better.

                            Also, an easy solution could be to use only one global pMeterInfo.
                            Yep, I added the following code and it worked as expected. Thanks Pierre.

                            Code:
                            CASE %WM_DESTROY
                            ' To avoid a long delay to end the program we must clean it out properly here with:
                            ' Thanks to Pierre Bellisle
                            POKE DWORD, VARPTR(pMeterInfo), 0
                            Francisco J Castanedo
                            Software Developer
                            Distribuidora 3HP, C.A.
                            [URL]http://www.distribuidora3hp.com[/URL]

                            Comment


                            • #15
                              Originally posted by Jim Fritts View Post
                              Francisco,
                              Here is an SDK version that outputs to a progress bar. Have not noticed any lingering.
                              Well, I must point out that the code I posted is 100% SDK too. In fact I had never ever use an RC file to create and draw a Dialog!!.

                              It's a nice refinement you did to the program Jim
                              Francisco J Castanedo
                              Software Developer
                              Distribuidora 3HP, C.A.
                              [URL]http://www.distribuidora3hp.com[/URL]

                              Comment


                              • #16
                                Thank you. I liked the idea so much I'm using it now for my apps that have a microphone. See it soon in the next version of xBot. See ya

                                Comment


                                • #17
                                  Even if trouble seems gone, I was not really comfortable with the "POKE DWORD, VARPTR(pMeterInfo), 0" solution.
                                  In the context of transposing C++ code, using a IAudioMeterInformation interface pointer like *pMeterInfo is not doable as is under PowerBASIC, it have to be, let's say.. cloned.

                                  So I did a search and found this...

                                  Fred Harris ObjPtr() Example, VTables, and Object Memory - CALL DWORD VTable USING ...

                                  and this from Stefan Schnell & José How to reference an object with reverse ObjPtr?

                                  In this last one, José show how to do it by adding a ref.
                                  Code:
                                  CASE %WM_INITDIALOG
                                    pMeterInfo = NOTHING
                                    POKE DWORD, VARPTR(pMeterInfo), lParam
                                    IF ISOBJECT(pMeterInfo) THEN pMeterInfo.AddRef
                                    ...
                                  
                                  CASE %WM_DESTROY
                                    pMeterInfo = NOTHING

                                  Comment


                                  • #18
                                    Pierre, I tried it out and it works fine. Since you know more on the subject that I do, I'll follow your lead.

                                    Thanks again for your valuable help.

                                    I also noted that a 125 msec delay for the timer to update the meter is too long a delay. Many peak measurements are lost using this delay. Best results are obtained by using a 1 msec update delay instead.
                                    Francisco J Castanedo
                                    Software Developer
                                    Distribuidora 3HP, C.A.
                                    [URL]http://www.distribuidora3hp.com[/URL]

                                    Comment


                                    • #19
                                      For the fun of it, here is a DDT version...
                                      Code:
                                      #COMPILE EXE 'Use of José Roca includes files
                                      #DIM ALL
                                      
                                      #INCLUDE "Windows.inc"
                                      #INCLUDE "mmDeviceApi.inc"
                                      #INCLUDE "EndPointVolume.inc"
                                      '#RESOURCE ".pbr" 'Add a manifest
                                      
                                      $AppName         = "MikePeakMeter"
                                      %StaticVolMin    = 101
                                      %StaticVolMax    = 102
                                      %StaticPeakMeter = 201
                                      %Timer01         = 301
                                      %TimerPeriod     = 025
                                      
                                      GLOBAL hDlg AS DWORD
                                      '_____________________________________________________________________________
                                      
                                      CALLBACK FUNCTION DlgProc
                                       STATIC pEnumerator   AS IMMDeviceEnumerator
                                       STATIC pDevice       AS IMMDevice
                                       STATIC pMeterInfo    AS IAudioMeterInformation
                                       LOCAL  PeakMeterRect AS RECT
                                       STATIC Peak          AS SINGLE
                                       STATIC hPeakMeter    AS DWORD
                                       STATIC hDc           AS DWORD
                                       STATIC hWhiteBrush   AS DWORD
                                       STATIC hGreenBrush   AS DWORD
                                      
                                       SELECT CASE CBMSG
                                      
                                         CASE %WM_INITDIALOG
                                           hPeakMeter  = GetDlgItem(hDlg, %StaticPeakMeter)
                                           hDc         = GetDC(hPeakMeter)
                                           hWhiteBrush = CreateSolidBrush(%RGB_WHITE)
                                           hGreenBrush = CreateSolidBrush(%RGB_GREEN)
                                      
                                           pEnumerator = NOTHING
                                           pDevice     = NOTHING
                                           pMeterInfo  = NOTHING
                                      
                                           'Get enumerator for audio endpoint devices
                                           IF CoCreateInstance($CLSID_MMDeviceEnumerator, NOTHING, %CLSCTX_INPROC_SERVER, _
                                                               $IID_IMMDeviceEnumerator, pEnumerator) = %S_OK THEN
                                             'Get peak meter for default audio-rendering device.
                                             'Here we can use either %EDataFlow_eRender (output stream) or %EDataFlow_eCapture (input stream)
                                             IF pEnumerator.GetDefaultAudioEndpoint(%EDataFlow_eRender, %DEVICE_STATE_ACTIVE, pDevice) = %S_OK THEN
                                               IF pDevice.Activate($IID_IAudioMeterInformation, %CLSCTX_ALL, $NUL, pMeterInfo) = %S_OK THEN
                                                 BEEP
                                               END IF
                                             END IF
                                           END IF
                                      
                                           SetTimer(hDlg, %Timer01, %TimerPeriod, 0)
                                           IF ISNOTHING(pMeterInfo) THEN
                                             PostMessage(hDlg, %WM_APP, 0, 0)
                                           END IF
                                      
                                         CASE %WM_APP
                                           MessageBox(hDlg, "Error in WM_INITDIALOG, program will exit!", $AppName, %MB_OK OR %MB_TOPMOST)
                                           DIALOG END hDlg, -1 'DIALOG END is not allowed in WM_INITDIALOG
                                      
                                         CASE %WM_COMMAND
                                           SELECT CASE CBCTL
                                      
                                             CASE %IDOK
                                               IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                                                 BEEP 'Do a sound
                                               END IF
                                      
                                             CASE %IDCANCEL
                                               IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                                                 DIALOG END hDlg, 0 'Exit program
                                               END IF
                                      
                                           END SELECT
                                      
                                         CASE %WM_TIMER
                                           IF CBWPARAM = %Timer01 THEN
                                             IF SUCCEEDED(pMeterInfo.GetPeakValue(Peak)) THEN
                                               GOSUB DrawPeakMeter 'Gosub is fast
                                             ELSE
                                               MessageBox(hDlg, "Error in timer, program will exit!", $AppName, %MB_OK OR %MB_TOPMOST)
                                               DIALOG END hDlg, -2
                                             END IF
                                           END IF
                                      
                                         CASE %WM_PAINT 'Redraw the peak meter.
                                           ValidateRect(hPeakMeter, $NUL)
                                           GOSUB DrawPeakMeter 'Gosub is fast
                                      
                                         CASE %WM_DESTROY 'Clean up
                                           pEnumerator = NOTHING
                                           pDevice     = NOTHING
                                           pMeterInfo  = NOTHING
                                           ReleaseDC(hPeakMeter, hDc)
                                           DeleteObject(hWhiteBrush)
                                           DeleteObject(hGreenBrush)
                                           KillTimer(hDlg, %Timer01)
                                      
                                        END SELECT
                                      
                                       EXIT FUNCTION '--------------------------------------------------------------
                                      
                                       DrawPeakMeter: 'Update the peak meter
                                         GetClientRect(hPeakMeter, PeakMeterRect)
                                         FillRect(hDc, PeakMeterRect, hWhiteBrush) 'Paint the entire bar
                                         INCR PeakMeterRect.nLeft
                                         INCR PeakMeterRect.nTop
                                         DECR PeakMeterRect.nBottom
                                         PeakMeterRect.nRight = PeakMeterRect.nLeft + MAX(0, (Peak * (PeakMeterRect.nRight - PeakMeterRect.nLeft) - 1.5))
                                         FillRect(hDc, PeakMeterRect, hGreenBrush)
                                       RETURN
                                      
                                      END FUNCTION
                                      '_____________________________________________________________________________
                                      
                                      FUNCTION PBMAIN()
                                       LOCAL hIcon AS DWORD
                                      
                                       DIALOG FONT "Arial Rounded MT Bold", 8
                                       DIALOG NEW %HWND_DESKTOP, $AppName, , , 150, 35, %WS_CAPTION OR %WS_MINIMIZEBOX OR %WS_SYSMENU, 0 TO hDlg
                                      
                                       hIcon = ExtractIcon(GetModuleHandle(""), "DDORes.dll", 3)
                                       SetClassLong(hDlg, %GCL_HICONSM, hIcon)
                                       SetClassLong(hDlg, %GCL_HICON, hIcon)
                                      
                                       CONTROL ADD LABEL, hDlg, %StaticVolMin, "Min", 10, 12, 20, 12, %SS_CENTER
                                       CONTROL ADD LABEL, hDlg, %StaticVolMax, "Max", 120, 12, 20, 12, %SS_CENTER
                                       CONTROL ADD LABEL, hDlg, %StaticPeakMeter , "", 34, 14, 82, 5, %SS_CENTER
                                      
                                       DIALOG SHOW MODAL hDlg CALL DlgProc
                                      
                                       DestroyIcon(hIcon)
                                      
                                      END FUNCTION
                                      '_____________________________________________________________________________
                                      '

                                      Comment


                                      • #20
                                        It's kinda different!!

                                        It is a way of COM Object programming I hadn't seen but I like it. Is like a new language altogether!!

                                        I like that GOSUB . I tend to use it more often nowadays. I agree with you in that it is faster, simpler and easier than writting a Function or Sub for small tasks. It is almost as the assembler JMP instruction. I also use some GOTOs here and there, this is the real JMP instruction of BASIC. I also use GLOBALS as needed and when used in an organized way they are invaluable.

                                        I like the way you write your code, very neat, easily readable and very well organized.

                                        I'm going to need a tutorial (i can understand) for this stuff. It is becoming a must.

                                        Thanks for posting it Pierre.
                                        Francisco J Castanedo
                                        Software Developer
                                        Distribuidora 3HP, C.A.
                                        [URL]http://www.distribuidora3hp.com[/URL]

                                        Comment

                                        Working...
                                        X