none
What control is microsoft using for the master volume visualization? RRS feed

  • Question

  • Hi,

    I have been really struggling to create a master system volume visualization that either isn't terribly slow (using a progressBar) or doesn't completely block my program (using NAUDIO). Microsoft seems capable of making them real time and animate quickly. (just look at the sound applet in the control panel) 

    Does anyone have any idea what control Microsoft is using to create this?

    here was my attempt:

    var capture = new WasapiLoopbackCapture();
    capture.DataAvailable += OnDataAvailable;
    capture.StartRecording();
     void OnDataAvailable(object sender, WaveInEventArgs args)
                {
                    float max = 0;
                    var buffer = new WaveBuffer(args.Buffer);
                    for (int index = 0; index < args.BytesRecorded / 4; index++)
                    {
                        var sample = buffer.FloatBuffer[index];
                        if (sample < 0) sample = -sample;
                        if (sample > max) max = sample;
                        double level = 100 * max;
                        uiContext.Send(x => progressBar.Value = level, null);
                    }

    The problem(s) with the code above is that the progressbar update takes forever so it is always behind and also when you close the Window it keeps running forever.

    Tuesday, December 10, 2019 5:55 PM

All replies

  • If you're talking about this, it is just a ListView with a Timer (drawing in CDDS_ITEMPOSTPAINT) :

    Tuesday, December 10, 2019 6:32 PM
  • actually, talking about this: 

    Wondering how both they are monitoring it and how they are making the bar bounce around.

    Tuesday, December 10, 2019 7:26 PM
  • It uses a similar method as described at Peak Meters
    Tuesday, December 10, 2019 8:54 PM
  • Yeah that is a little closer, I built it in C++ now I just have to figure out how to make that work in c# haha, I am just now learning c# so it's been a bit of a struggle bus ride.
    Tuesday, December 10, 2019 9:47 PM
  • Ah, so that was easier than I thought:

    Ended up with this bound to a 125ms timer.

    private void AudioTimer_Tick(object sender, EventArgs e)
            {
                pDevice = pEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Console);
                pMeterInfo = pDevice.AudioMeterInformation;
                float peak = pMeterInfo.MasterPeakValue;
                progressBar.Value = peak * 100;
            }

    Its still pretty laggy compared to whatever control Windows is using. Is there any way to know for sure how they are drawing that bar?

    Tuesday, December 10, 2019 10:35 PM
  • I converted the MS C++ sample into C#, with P/Invoke

    It doesn't seem laggy on my OS (Windows 10) =>

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace CSharp_PeakMeter
    {
        public partial class Form1 : Form
        {
            public enum HRESULT : int
            {
                S_OK = 0,
                S_FALSE = 1,
                E_NOINTERFACE = unchecked((int)0x80004002),
                E_NOTIMPL = unchecked((int)0x80004001),
                E_FAIL = unchecked((int)0x80004005),
                E_UNEXPECTED = unchecked((int)0x8000FFFF),
                E_OUTOFMEMORY = unchecked((int)0x8007000E)
            }
    
            [ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IPropertyStore
            {
                HRESULT GetCount([Out] out uint propertyCount);
                HRESULT GetAt([In] uint propertyIndex, [Out, MarshalAs(UnmanagedType.Struct)] out PROPERTYKEY key);
                HRESULT GetValue([In, MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [Out, MarshalAs(UnmanagedType.Struct)] out PROPVARIANT pv);
                HRESULT SetValue([In, MarshalAs(UnmanagedType.Struct)] ref PROPERTYKEY key, [In, MarshalAs(UnmanagedType.Struct)] ref PROPVARIANT pv);
                HRESULT Commit();
            }
    
            public struct PROPERTYKEY
            {
                public PROPERTYKEY(Guid InputId, uint InputPid)
                {
                    fmtid = InputId;
                    pid = InputPid;
                }
                Guid fmtid;
                uint pid;
            };
    
            public enum EDataFlow
            {
                eRender = 0,
                eCapture = (eRender + 1),
                eAll = (eCapture + 1),
                EDataFlow_enum_count = (eAll + 1)
            }
            public enum ERole
            {
                eConsole = 0,
                eMultimedia = (eConsole + 1),
                eCommunications = (eMultimedia + 1),
                ERole_enum_count = (eCommunications + 1)
            }
    
            [ComImport]
            [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IMMDeviceEnumerator
            {
                HRESULT EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, out IMMDeviceCollection ppDevices);
                HRESULT GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppEndpoint);
                HRESULT GetDevice(string pwstrId, out IMMDevice ppDevice);
                HRESULT RegisterEndpointNotificationCallback(IMMNotificationClient pClient);
                HRESULT UnregisterEndpointNotificationCallback(IMMNotificationClient pClient);
            }
    
            public const int DEVICE_STATE_ACTIVE = 0x00000001;
            public const int DEVICE_STATE_DISABLED = 0x00000002;
            public const int DEVICE_STATE_NOTPRESENT = 0x00000004;
            public const int DEVICE_STATE_UNPLUGGED = 0x00000008;
            public const int DEVICE_STATEMASK_ALL = 0x0000000f;
    
            [ComImport]
            [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IMMDeviceCollection
            {
                HRESULT GetCount(out uint pcDevices);
                HRESULT Item(uint nDevice, out IMMDevice ppDevice);
            }
    
            [ComImport]
            [Guid("D666063F-1587-4E43-81F1-B948E807363F")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IMMDevice
            {
                HRESULT Activate(ref Guid iid, int dwClsCtx, ref PROPVARIANT pActivationParams, out IntPtr ppInterface);
                //HRESULT Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, out IntPtr ppInterface);
                HRESULT OpenPropertyStore(int stgmAccess, out IPropertyStore ppProperties);
                //HRESULT GetId(StringBuilder ppstrId);
                HRESULT GetId(out IntPtr ppstrId);
                HRESULT GetState(out int pdwState);
            }
    
            [ComImport]
            [Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IMMNotificationClient
            {
                HRESULT OnDeviceStateChanged(string pwstrDeviceId, int dwNewState);
                HRESULT OnDeviceAdded(string pwstrDeviceId);
                HRESULT OnDeviceRemoved(string pwstrDeviceId);
                HRESULT OnDefaultDeviceChanged(EDataFlow flow, ERole role, string pwstrDefaultDeviceId);
                HRESULT OnPropertyValueChanged(string pwstrDeviceId, ref PROPERTYKEY key);
            }
    
            [ComImport]
            [Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IAudioMeterInformation
            {
                HRESULT GetPeakValue(out float pfPeak);
                HRESULT GetMeteringChannelCount(out uint pnChannelCount);
                HRESULT GetChannelsPeakValues(uint u32ChannelCount, out float afPeakValues);
                HRESULT QueryHardwareSupport(out int pdwHardwareSupportMask);
            }
    
    
            [StructLayout(LayoutKind.Sequential)]
            public struct PROPARRAY
            {
                public UInt32 cElems;
                public IntPtr pElems;
            }
    
            [StructLayout(LayoutKind.Explicit, Pack = 1)]
            public struct PROPVARIANT
            {
                [FieldOffset(0)]
                public ushort varType;
                [FieldOffset(2)]
                public ushort wReserved1;
                [FieldOffset(4)]
                public ushort wReserved2;
                [FieldOffset(6)]
                public ushort wReserved3;
    
                [FieldOffset(8)]
                public byte bVal;
                [FieldOffset(8)]
                public sbyte cVal;
                [FieldOffset(8)]
                public ushort uiVal;
                [FieldOffset(8)]
                public short iVal;
                [FieldOffset(8)]
                public UInt32 uintVal;
                [FieldOffset(8)]
                public Int32 intVal;
                [FieldOffset(8)]
                public UInt64 ulVal;
                [FieldOffset(8)]
                public Int64 lVal;
                [FieldOffset(8)]
                public float fltVal;
                [FieldOffset(8)]
                public double dblVal;
                [FieldOffset(8)]
                public short boolVal;
                [FieldOffset(8)]
                public IntPtr pclsidVal; // GUID ID pointer
                [FieldOffset(8)]
                public IntPtr pszVal; // Ansi string pointer
                [FieldOffset(8)]
                public IntPtr pwszVal; // Unicode string pointer
                [FieldOffset(8)]
                public IntPtr punkVal; // punkVal (interface pointer)
                [FieldOffset(8)]
                public PROPARRAY ca;
                [FieldOffset(8)]
                public System.Runtime.InteropServices.ComTypes.FILETIME filetime;
            }
    
    
            public Form1()
            {
                //InitializeComponent();
                this.Text = "Peak Meter";
                this.Load += new System.EventHandler(this.Form1_Load);
                this.ResizeRedraw = true;
            }
    
            private IAudioMeterInformation pAudioMeterInformation = null;
            private static System.Timers.Timer aTimer;
            private IntPtr hPeakMeter = IntPtr.Zero;
            private float nPeak = 0;
    
            private void Form1_Load(object sender, EventArgs e)
            {
                HRESULT hr = HRESULT.E_FAIL;
                Guid CLSID_MMDeviceEnumerator = new Guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}");
                Type MMDeviceEnumeratorType = Type.GetTypeFromCLSID(CLSID_MMDeviceEnumerator, true);
                object MMDeviceEnumerator = Activator.CreateInstance(MMDeviceEnumeratorType);
                IMMDeviceEnumerator pMMDeviceEnumerator = (IMMDeviceEnumerator)MMDeviceEnumerator;
                if (pMMDeviceEnumerator != null)
                {
                    IMMDevice pDevice = null;
                    hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out pDevice);
                    //hr = pMMDeviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eCapture, ERole.eMultimedia, out pDevice);
                    if (hr == HRESULT.S_OK)
                    {
                        IntPtr pIAudioMeterInformationPtr = IntPtr.Zero;
                        PROPVARIANT pvActivate = new PROPVARIANT();
                        hr = pDevice.Activate(typeof(IAudioMeterInformation).GUID, 0, ref pvActivate, out pIAudioMeterInformationPtr);
                        if (hr == HRESULT.S_OK)
                        {
                            pAudioMeterInformation = Marshal.GetObjectForIUnknown(pIAudioMeterInformationPtr) as IAudioMeterInformation;
                        }
                    }
                    Marshal.ReleaseComObject(pMMDeviceEnumerator);
                }
    
                hPeakMeter = this.Handle;
    
                aTimer = new System.Timers.Timer(125);
                aTimer.Elapsed += OnTimedEvent;
                aTimer.AutoReset = true;
                aTimer.Enabled = true;
                aTimer.Start();
    
                CenterToScreen();
            }
    
            private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
            {
                HRESULT hr = HRESULT.E_FAIL;
                hr = pAudioMeterInformation.GetPeakValue(out nPeak);
                DrawPeakMeter(hPeakMeter, nPeak);
            }
    
            private void DrawPeakMeter(IntPtr hPeakMeter, float nPeak)
            {
                using (Graphics gr = Graphics.FromHwnd(hPeakMeter))
                {
                    Rectangle rect = ClientRectangle;
                    rect.Inflate(-1, -Height/2 + 30 );
                    using (SolidBrush brush = new SolidBrush(Color.Black))
                    {
                        gr.FillRectangle(brush, rect);
                    }
                    Rectangle rectPeak = new Rectangle(rect.Left + 1, rect.Top + 1, (int)(rect.Left + Math.Max(0, (nPeak * (rect.Right - rect.Left) - 1.5))) - rect.Left, (rect.Bottom -rect.Top) - 1);
                    using (SolidBrush brush = new SolidBrush(Color.Chartreuse))
                    {
                        gr.FillRectangle(brush, rectPeak);
                    }
                }
            }
        }
    }
    

    • Proposed as answer by tommytwotrain Wednesday, December 11, 2019 7:47 PM
    Wednesday, December 11, 2019 11:20 AM
  • So my app is a WPF app so I was trying to do this using a WPF control.

    Is the difference because im using a progreasBar and you are using a rectangle?

    Wednesday, December 11, 2019 7:15 PM
  • So my app is a WPF app so I was trying to do this using a WPF control.

    Is the difference because im using a progreasBar and you are using a rectangle?

    Very good Castor!

    Yes drawing a rectangle is going to give faster response. The progress bar is famous for that.

    Also set the timer to 30ms for better effect.

       aTimer = new System.Timers.Timer(30);

    Here is the example on my  win7 system at 30ms with a heavy song.


    PS actually the animation is only running 100ms.
    Wednesday, December 11, 2019 7:47 PM
  • Any thoughts on styling it like the original example?
    Wednesday, December 11, 2019 7:55 PM
  • Any thoughts on styling it like the original example?

    What do you mean exactly by styling?

    Castor's example works off the speakers I believe. Your first example uses a wav file you are capturing?

    Do you make or have a bitarray? You then average the values more or less the same way. What is your WaveInEventArgs and WaveBuffer in the first example?

    Here is some discussion in the vb forum using a wav file.

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/fa0372f2-8668-40cf-a3f2-32ea44f59053/how-to-draw-a-wav-file-volume-meter?forum=vbgeneral

    I don't how to do it in WPF if that is what you mean?

    I was just playing along...

    Let's see what Castor says.


    Wednesday, December 11, 2019 8:23 PM
  • The Sound Settings on Windows 10 uses a standard UWP Slider (Sliders)

    (you can see it with MS Inspect Tool (for UIAutomation))

    I don't use UWP, but it should be easy to do the same effect by drawing over with a timer...


    • Edited by Castorix31 Wednesday, December 11, 2019 8:54 PM
    Wednesday, December 11, 2019 8:54 PM
  • Is that just the actual slider part or the bar under the slider showing the output?

    Wednesday, December 11, 2019 9:19 PM
  • If I zoom, it draws a rectangle over the Slider bar :



    • Edited by Castorix31 Thursday, December 12, 2019 7:51 AM
    Thursday, December 12, 2019 7:50 AM