using System;
using System.Runtime.InteropServices;

namespace ryCommon
{
    /// <summary>
    /// SOURCE CODE FROM THE WEBSITE.    
    /// LAST UPDATE:WWW.VJSDN.COM易学论坛,2009/08/03 by Jonny Sun
    /// </summary>
    public class SoundControl
    {
        #region declarations

        const int MAXPNAMELEN = 32;
        const int MIXER_SHORT_NAME_CHARS = 16;
        const int MIXER_LONG_NAME_CHARS = 64;

        [Flags]
        enum MIXERLINE_LINEF : uint
        {
            ACTIVE = 0x00000001,
            DISCONNECTED = 0x00008000,
            SOURCE = 0x80000000
        }

        [Flags]
        enum MIXER : uint
        {
            GETLINEINFOF_DESTINATION = 0x00000000,
            GETLINEINFOF_SOURCE = 0x00000001,
            GETLINEINFOF_LINEID = 0x00000002,
            GETLINEINFOF_COMPONENTTYPE = 0x00000003,
            GETLINEINFOF_TARGETTYPE = 0x00000004,
            GETLINEINFOF_QUERYMASK = 0x0000000F,

            GETLINECONTROLSF_ALL = 0x00000000,
            GETLINECONTROLSF_ONEBYID = 0x00000001,
            GETLINECONTROLSF_ONEBYTYPE = 0x00000002,
            GETLINECONTROLSF_QUERYMASK = 0x0000000F,

            GETCONTROLDETAILSF_VALUE = 0x00000000,
            GETCONTROLDETAILSF_LISTTEXT = 0x00000001,
            GETCONTROLDETAILSF_QUERYMASK = 0x0000000F,

            OBJECTF_MIXER = 0x00000000,
            OBJECTF_WAVEOUT = 0x10000000,
            OBJECTF_WAVEIN = 0x20000000,
            OBJECTF_MIDIOUT = 0x30000000,
            OBJECTF_MIDIIN = 0x40000000,
            OBJECTF_AUX = 0x50000000,
            OBJECTF_HANDLE = 0x80000000,
            OBJECTF_HMIXER = OBJECTF_HANDLE | OBJECTF_MIXER,
            OBJECTF_HWAVEOUT = OBJECTF_HANDLE | OBJECTF_WAVEOUT,
            OBJECTF_HWAVEIN = OBJECTF_HANDLE | OBJECTF_WAVEIN,
            OBJECTF_HMIDIOUT = OBJECTF_HANDLE | OBJECTF_MIDIOUT,
            OBJECTF_HMIDIIN = OBJECTF_HANDLE | OBJECTF_MIDIIN
        }

        [Flags]
        enum MIXERCONTROL_CT : uint
        {
            CLASS_MASK = 0xF0000000,
            CLASS_CUSTOM = 0x00000000,
            CLASS_METER = 0x10000000,
            CLASS_SWITCH = 0x20000000,
            CLASS_NUMBER = 0x30000000,
            CLASS_SLIDER = 0x40000000,
            CLASS_FADER = 0x50000000,
            CLASS_TIME = 0x60000000,
            CLASS_LIST = 0x70000000,

            SUBCLASS_MASK = 0x0F000000,

            SC_SWITCH_BOOLEAN = 0x00000000,
            SC_SWITCH_BUTTON = 0x01000000,

            SC_METER_POLLED = 0x00000000,

            SC_TIME_MICROSECS = 0x00000000,
            SC_TIME_MILLISECS = 0x01000000,

            SC_LIST_SINGLE = 0x00000000,
            SC_LIST_MULTIPLE = 0x01000000,

            UNITS_MASK = 0x00FF0000,
            UNITS_CUSTOM = 0x00000000,
            UNITS_BOOLEAN = 0x00010000,
            UNITS_SIGNED = 0x00020000,
            UNITS_UNSIGNED = 0x00030000,
            UNITS_DECIBELS = 0x00040000, /* in 10ths */
            UNITS_PERCENT = 0x00050000, /* in 10ths */
        }

        [Flags]
        enum MIXERCONTROL_CONTROLTYPE : uint
        {
            CUSTOM = MIXERCONTROL_CT.CLASS_CUSTOM | MIXERCONTROL_CT.UNITS_CUSTOM,
            BOOLEANMETER = MIXERCONTROL_CT.CLASS_METER | MIXERCONTROL_CT.SC_METER_POLLED | MIXERCONTROL_CT.UNITS_BOOLEAN,
            SIGNEDMETER = MIXERCONTROL_CT.CLASS_METER | MIXERCONTROL_CT.SC_METER_POLLED | MIXERCONTROL_CT.UNITS_SIGNED,
            PEAKMETER = SIGNEDMETER + 1,
            UNSIGNEDMETER = MIXERCONTROL_CT.CLASS_METER | MIXERCONTROL_CT.SC_METER_POLLED | MIXERCONTROL_CT.UNITS_UNSIGNED,
            BOOLEAN = MIXERCONTROL_CT.CLASS_SWITCH | MIXERCONTROL_CT.SC_SWITCH_BOOLEAN | MIXERCONTROL_CT.UNITS_BOOLEAN,
            ONOFF = BOOLEAN + 1,
            MUTE = BOOLEAN + 2,
            MONO = BOOLEAN + 3,
            LOUDNESS = BOOLEAN + 4,
            STEREOENH = BOOLEAN + 5,
            BASS_BOOST = BOOLEAN + 0x00002277,
            BUTTON = MIXERCONTROL_CT.CLASS_SWITCH | MIXERCONTROL_CT.SC_SWITCH_BUTTON | MIXERCONTROL_CT.UNITS_BOOLEAN,
            DECIBELS = MIXERCONTROL_CT.CLASS_NUMBER | MIXERCONTROL_CT.UNITS_DECIBELS,
            SIGNED = MIXERCONTROL_CT.CLASS_NUMBER | MIXERCONTROL_CT.UNITS_SIGNED,
            UNSIGNED = MIXERCONTROL_CT.CLASS_NUMBER | MIXERCONTROL_CT.UNITS_UNSIGNED,
            PERCENT = MIXERCONTROL_CT.CLASS_NUMBER | MIXERCONTROL_CT.UNITS_PERCENT,
            SLIDER = MIXERCONTROL_CT.CLASS_SLIDER | MIXERCONTROL_CT.UNITS_SIGNED,
            PAN = SLIDER + 1,
            QSOUNDPAN = SLIDER + 2,
            FADER = MIXERCONTROL_CT.CLASS_FADER | MIXERCONTROL_CT.UNITS_UNSIGNED,
            VOLUME = FADER + 1,
            BASS = FADER + 2,
            TREBLE = FADER + 3,
            EQUALIZER = FADER + 4,
            SINGLESELECT = MIXERCONTROL_CT.CLASS_LIST | MIXERCONTROL_CT.SC_LIST_SINGLE | MIXERCONTROL_CT.UNITS_BOOLEAN,
            MUX = SINGLESELECT + 1,
            MULTIPLESELECT = MIXERCONTROL_CT.CLASS_LIST | MIXERCONTROL_CT.SC_LIST_MULTIPLE | MIXERCONTROL_CT.UNITS_BOOLEAN,
            MIXER = MULTIPLESELECT + 1,
            MICROTIME = MIXERCONTROL_CT.CLASS_TIME | MIXERCONTROL_CT.SC_TIME_MICROSECS | MIXERCONTROL_CT.UNITS_UNSIGNED,
            MILLITIME = MIXERCONTROL_CT.CLASS_TIME | MIXERCONTROL_CT.SC_TIME_MILLISECS | MIXERCONTROL_CT.UNITS_UNSIGNED
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct MIXERLINE
        {
            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
            public struct TargetInfo
            {
                public uint dwType;
                public uint dwDeviceID;
                public ushort wMid;
                public ushort wPid;
                public uint vDriverVersion;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAXPNAMELEN)]
                public string szPname;
            }

            public uint cbStruct;
            public uint dwDestination;
            public uint dwSource;
            public uint dwLineID;
            public MIXERLINE_LINEF fdwLine;
            public uint dwUser;
            public uint dwComponentType;
            public uint cChannels;
            public uint cConnection;
            public uint cControls;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MIXER_SHORT_NAME_CHARS)]
            public string szShortName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MIXER_LONG_NAME_CHARS)]
            public string szName;
            public TargetInfo Target;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct MIXERCONTROL
        {
            [StructLayout(LayoutKind.Explicit)]
            public struct BoundsInfo
            {
                [FieldOffset(0)]
                public int lMinimum;
                [FieldOffset(4)]
                public int lMaximum;
                [FieldOffset(0)]
                public uint dwMinimum;
                [FieldOffset(4)]
                public uint dwMaximum;
                [FieldOffset(8), MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
                public uint[] dwReserved;
            }
            [StructLayout(LayoutKind.Explicit)]
            public struct MetricsInfo
            {
                [FieldOffset(0)]
                public uint cSteps;
                [FieldOffset(0)]
                public uint cbCustomData;
                [FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
                public uint[] dwReserved;
            }

            public uint cbStruct;
            public uint dwControlID;
            public MIXERCONTROL_CONTROLTYPE dwControlType;
            public uint fdwControl;
            public uint cMultipleItems;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MIXER_SHORT_NAME_CHARS)]
            public string szShortName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MIXER_LONG_NAME_CHARS)]
            public string szName;
            public BoundsInfo Bounds;
            public MetricsInfo Metrics;
        }

        [StructLayout(LayoutKind.Explicit)]
        struct MIXERLINECONTROLS
        {
            [FieldOffset(0)]
            public uint cbStruct;
            [FieldOffset(4)]
            public uint dwLineID;
            [FieldOffset(8)]
            public uint dwControlID;
            [FieldOffset(8)] // not a typo! overlaps previous field
            public uint dwControlType;
            [FieldOffset(12)]
            public uint cControls;
            [FieldOffset(16)]
            public uint cbmxctrl;
            [FieldOffset(20)]
            public IntPtr pamxctrl;
        }

        [StructLayout(LayoutKind.Explicit)]
        struct MIXERCONTROLDETAILS
        {
            [FieldOffset(0)]
            public uint cbStruct;
            [FieldOffset(4)]
            public uint dwControlID;
            [FieldOffset(8)]
            public uint cChannels;
            [FieldOffset(12)]
            public IntPtr hwndOwner;
            [FieldOffset(12)] // not a typo!
            public uint cMultipleItems;
            [FieldOffset(16)]
            public uint cbDetails;
            [FieldOffset(20)]
            public IntPtr paDetails;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct VOLUME
        {
            public int left;
            public int right;
        }

        public struct MixerInfo
        {
            public uint volumeCtl;
            public uint muteCtl;
            public int minVolume;
            public int maxVolume;
        }

        [DllImport("WinMM.dll", CharSet = CharSet.Auto)]
        static extern uint mixerGetLineInfo(IntPtr hmxobj, ref MIXERLINE pmxl, MIXER flags);

        [DllImport("WinMM.dll", CharSet = CharSet.Auto)]
        static extern uint mixerGetLineControls(IntPtr hmxobj, ref MIXERLINECONTROLS pmxlc, MIXER flags);

        [DllImport("WinMM.dll", CharSet = CharSet.Auto)]
        static extern uint mixerGetControlDetails(IntPtr hmxobj, ref MIXERCONTROLDETAILS pmxcd, MIXER flags);

        [DllImport("WinMM.dll", CharSet = CharSet.Auto)]
        static extern uint mixerSetControlDetails(IntPtr hmxobj, ref MIXERCONTROLDETAILS pmxcd, MIXER flags);

        #endregion declarations

        #region private methods

        public static MixerInfo GetMixerControls()
        {
            MIXERLINE mxl = new MIXERLINE();
            MIXERLINECONTROLS mlc = new MIXERLINECONTROLS();
            mxl.cbStruct = (uint)Marshal.SizeOf(typeof(MIXERLINE));
            mlc.cbStruct = (uint)Marshal.SizeOf(typeof(MIXERLINECONTROLS));

            mixerGetLineInfo(IntPtr.Zero, ref mxl, MIXER.OBJECTF_MIXER | MIXER.GETLINEINFOF_DESTINATION);

            mlc.dwLineID = mxl.dwLineID;
            mlc.cControls = mxl.cControls;
            mlc.cbmxctrl = (uint)Marshal.SizeOf(typeof(MIXERCONTROL));
            mlc.pamxctrl = Marshal.AllocHGlobal((int)(mlc.cbmxctrl * mlc.cControls));

            mixerGetLineControls(IntPtr.Zero, ref mlc, MIXER.OBJECTF_MIXER | MIXER.GETLINECONTROLSF_ALL);

            MixerInfo rtn = new MixerInfo();

            for (int i = 0; i < mlc.cControls; i++)
            {
                MIXERCONTROL mxc = (MIXERCONTROL)Marshal.PtrToStructure((IntPtr)((int)mlc.pamxctrl + (int)mlc.cbmxctrl * i), typeof(MIXERCONTROL));
                switch (mxc.dwControlType)
                {
                    case MIXERCONTROL_CONTROLTYPE.VOLUME:
                        rtn.volumeCtl = mxc.dwControlID;
                        rtn.minVolume = mxc.Bounds.lMinimum;
                        rtn.maxVolume = mxc.Bounds.lMaximum;
                        break;
                    case MIXERCONTROL_CONTROLTYPE.MUTE:
                        rtn.muteCtl = mxc.dwControlID;
                        break;
                }
            }

            Marshal.FreeHGlobal(mlc.pamxctrl);

            return rtn;
        }
        static VOLUME GetVolume(MixerInfo mi)
        {
            MIXERCONTROLDETAILS mcd = new MIXERCONTROLDETAILS
            {
                cbStruct = (uint)Marshal.SizeOf(typeof(MIXERCONTROLDETAILS)),
                dwControlID = mi.volumeCtl,
                cMultipleItems = 0,
                cChannels = 2,
                cbDetails = (uint)Marshal.SizeOf(typeof(VOLUME))
            };
            mcd.paDetails = Marshal.AllocHGlobal((int)mcd.cbDetails);

            mixerGetControlDetails(IntPtr.Zero, ref mcd, MIXER.GETCONTROLDETAILSF_VALUE | MIXER.OBJECTF_MIXER);

            VOLUME rtn = (VOLUME)Marshal.PtrToStructure(mcd.paDetails, typeof(VOLUME));

            Marshal.FreeHGlobal(mcd.paDetails);

            return rtn;
        }
        public static bool IsMuted(MixerInfo mi)
        {
            MIXERCONTROLDETAILS mcd = new MIXERCONTROLDETAILS
            {
                cbStruct = (uint)Marshal.SizeOf(typeof(MIXERCONTROLDETAILS)),
                dwControlID = mi.muteCtl,
                cMultipleItems = 0,
                cChannels = 1,
                cbDetails = 4
            };
            mcd.paDetails = Marshal.AllocHGlobal((int)mcd.cbDetails);

            mixerGetControlDetails(IntPtr.Zero, ref mcd, MIXER.GETCONTROLDETAILSF_VALUE | MIXER.OBJECTF_MIXER);

            int rtn = Marshal.ReadInt32(mcd.paDetails);

            Marshal.FreeHGlobal(mcd.paDetails);

            return rtn != 0;
        }

        static void MuteIt(MixerInfo mi, bool left, bool right)
        {
            VOLUME volume = GetVolume(mi);
            if (!left && !right) // mute off
            {
                volume.left = 65530;
                volume.right = 65530;
            }
            else if (left) //mute left channel
            {
                volume.left = 0;
                volume.right = 65530;
            }
            else if (right) // mute right channel
            {
                volume.left = 65530;
                volume.right = 0;
            }

            SetVolume(mi, volume);
        }

        static void SetVolume(MixerInfo mi, VOLUME volume)
        {
            MIXERCONTROLDETAILS mcd = new MIXERCONTROLDETAILS
            {
                cbStruct = (uint)Marshal.SizeOf(typeof(MIXERCONTROLDETAILS)),
                dwControlID = mi.volumeCtl,
                cMultipleItems = 0,
                cChannels = 2,
                cbDetails = (uint)Marshal.SizeOf(typeof(VOLUME))
            };
            mcd.paDetails = Marshal.AllocHGlobal((int)mcd.cbDetails);

            Marshal.StructureToPtr(volume, mcd.paDetails, false);

            mixerSetControlDetails(IntPtr.Zero, ref mcd, MIXER.GETCONTROLDETAILSF_VALUE | MIXER.OBJECTF_MIXER);

            Marshal.FreeHGlobal(mcd.paDetails);
        }

        #endregion private methods

        #region public methods

        /// <summary>
        /// Mutes the left hand channel and transfers audio fully to the right
        /// </summary>
        public void MuteLeftChannel()
        {
            MixerInfo mi = GetMixerControls();
            MuteIt(mi, false, true);
        }

        /// <summary>
        /// Mutes the right hand channel and transfers audio fully to the left
        /// </summary>
        public void MuteRightChannel()
        {
            MixerInfo mi = GetMixerControls();
            MuteIt(mi, true, false);
        }

        public void MuteOff()
        {
            MixerInfo mi = GetMixerControls();
            MuteIt(mi, false, false);
        }

        #endregion public methods
    }
}